Merge remote-tracking branch 'upstream/master' into airbrake-backend
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': minor
|
||||
---
|
||||
|
||||
The `callbackUrl` option of `OAuthAdapter` is now required.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-newrelic-dashboard': patch
|
||||
---
|
||||
|
||||
Export DashboardSnapshotComponent from new-relic-dashboard-plugin
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Removed unnecessary `get-port` dependency
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Adjust ErrorPage to accept optional supportUrl property to override app support config. Update type of additionalInfo property to be ReactNode to accept both string and component.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Adds a loading bar to the scaffolder task page if the task is still loading. This can happen if it takes a while for a task worker to pick up a task.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
The experimental types build enabled by `--experimental-type-build` now runs in a separate worker thread.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
The file path printed by the default lint formatter is now relative to the repository root, rather than the individual package.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Added a new `cookieConfigurer` option to `AuthProviderConfig` that makes it possible to override the default logic for configuring OAuth provider cookies.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Switched the `file:` dependency for a `link:` dependency in the `backend` package. This makes sure that the `app` package is linked in rather than copied.
|
||||
|
||||
To apply this update to an existing app, make the following change to `packages/backend/package.json`:
|
||||
|
||||
```diff
|
||||
"dependencies": {
|
||||
- "app": "file:../app",
|
||||
+ "app": "link:../app",
|
||||
"@backstage/backend-common": "^{{version '@backstage/backend-common'}}",
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
---
|
||||
|
||||
Add userExpand option to allow users to expand fields retrieved from the Graph API - for use in custom transformers
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Removed the `import/no-duplicates` lint rule from the frontend and backend ESLint configurations. This rule is quite expensive to execute and only provides a purely cosmetic benefit, so we opted to remove it from the set of default rules. If you would like to keep this rule you can add it back in your local ESLint configuration:
|
||||
|
||||
```js
|
||||
'import/no-duplicates': 'warn'
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Rather than calling `yarn pack`, the `build-workspace` and `backend-bundle` commands now move files directly whenever possible. This cuts out several `yarn` invocations and speeds the packing process up by several orders of magnitude.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Switched the `lint` command to invoke ESLint directly through its Node.js API rather than spawning a new process.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Introduced an experimental and hidden `repo` sub-command, that contains commands that operate on an entire monorepo rather than individual packages.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@techdocs/cli': patch
|
||||
---
|
||||
|
||||
Updated the HTTP server to allow for simplification of the development of the CLI itself.
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
# Grabs the version in the root package.json and creates a tag on GitHub
|
||||
- name: Create a release tag
|
||||
id: create_tag
|
||||
run: node scripts/create-release-tag.js
|
||||
run: node scripts/create-release-tag.js --dispatch-workflows
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
|
||||
|
||||
|
||||
@@ -48,3 +48,31 @@ jobs:
|
||||
git add .
|
||||
git commit -am "${{ github.event.inputs.version }}"
|
||||
git push
|
||||
|
||||
- name: Dispatch update-helper update
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
|
||||
# TODO(Rugvip): Remove the create-app dispatch once we've been on the release version for a while
|
||||
script: |
|
||||
console.log('Dispatching upgrade helper sync - release version');
|
||||
await octokit.actions.createWorkflowDispatch({
|
||||
owner: 'backstage',
|
||||
repo: 'upgrade-helper-diff',
|
||||
workflow_id: 'release.yml',
|
||||
ref: 'master',
|
||||
inputs: {
|
||||
version: require('./package.json').version,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Dispatching upgrade helper sync - create-app version');
|
||||
await octokit.actions.createWorkflowDispatch({
|
||||
owner: 'backstage',
|
||||
repo: 'upgrade-helper-diff',
|
||||
workflow_id: 'release.yml',
|
||||
ref: 'master',
|
||||
inputs: {
|
||||
version: require('./packages/create-app/package.json').version,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ updated documentation next time you run Backstage!
|
||||
|
||||
## Writing and previewing your documentation
|
||||
|
||||
Using the [techdocs-cli](https://github.com/backstage/techdocs-cli) you can
|
||||
Using the [techdocs-cli](https://github.com/backstage/backstage/tree/master/packages/techdocs-cli) you can
|
||||
preview your docs inside a local Backstage instance and get live reload on
|
||||
changes. This is useful when you want to preview your documentation while
|
||||
writing.
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
"@types/unzipper": "^0.10.3",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"aws-sdk-mock": "^5.2.1",
|
||||
"get-port": "^5.1.1",
|
||||
"http-errors": "^2.0.0",
|
||||
"jest": "^26.0.1",
|
||||
"mock-fs": "^5.1.0",
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"dockerode": "^3.3.1",
|
||||
"example-app": "file:../app",
|
||||
"example-app": "link:../app",
|
||||
"express": "^4.17.1",
|
||||
"express-prom-bundle": "^6.3.6",
|
||||
"express-promise-router": "^4.1.0",
|
||||
|
||||
@@ -54,11 +54,10 @@ module.exports = {
|
||||
'no-console': 0, // Permitted in console programs
|
||||
'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()'
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: false,
|
||||
devDependencies: ['**/*.test.*', 'src/setupTests.*', 'dev/**'],
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
@@ -97,16 +96,6 @@ module.exports = {
|
||||
{
|
||||
files: ['*.test.*', 'src/setupTests.*', 'dev/**'],
|
||||
rules: {
|
||||
// Tests are allowed to import dev dependencies
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': ['error', ...globalRestrictedSyntax],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -49,11 +49,15 @@ module.exports = {
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'no-undef': 'off',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: false,
|
||||
devDependencies: [
|
||||
'**/*.test.*',
|
||||
'**/*.stories.*',
|
||||
'src/setupTests.*',
|
||||
'dev/**',
|
||||
],
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
@@ -101,20 +105,5 @@ module.exports = {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.test.*', '*.stories.*', 'src/setupTests.*', 'dev/**'],
|
||||
rules: {
|
||||
// Tests are allowed to import dev dependencies
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "3.0.4",
|
||||
"mini-css-extract-plugin": "^2.4.2",
|
||||
"npm-packlist": "^3.0.0",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"ora": "^5.3.0",
|
||||
"postcss": "^8.1.0",
|
||||
@@ -132,6 +133,7 @@
|
||||
"@types/minimatch": "^3.0.5",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/npm-packlist": "^1.1.2",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/rollup-plugin-peer-deps-external": "^2.2.0",
|
||||
"@types/rollup-plugin-postcss": "^2.0.0",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import fs from 'fs-extra';
|
||||
import { Command } from 'commander';
|
||||
import { buildBundle } from '../../lib/bundler';
|
||||
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
|
||||
import { getEnvironmentParallelism } from '../../lib/parallel';
|
||||
import { loadCliConfig } from '../../lib/config';
|
||||
import { paths } from '../../lib/paths';
|
||||
|
||||
@@ -25,7 +25,7 @@ export default async (cmd: Command) => {
|
||||
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
await buildBundle({
|
||||
entry: 'src/index',
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
parallelism: getEnvironmentParallelism(),
|
||||
statsJsonEnabled: cmd.stats,
|
||||
...(await loadCliConfig({
|
||||
args: cmd.config,
|
||||
|
||||
@@ -21,7 +21,7 @@ import tar, { CreateOptions } from 'tar';
|
||||
import { Command } from 'commander';
|
||||
import { createDistWorkspace } from '../../lib/packager';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
|
||||
import { getEnvironmentParallelism } from '../../lib/parallel';
|
||||
import { buildPackage, Output } from '../../lib/builder';
|
||||
|
||||
const BUNDLE_FILE = 'bundle.tar.gz';
|
||||
@@ -40,7 +40,7 @@ export default async (cmd: Command) => {
|
||||
targetDir: tmpDir,
|
||||
buildDependencies: Boolean(cmd.buildDependencies),
|
||||
buildExcludes: [pkg.name],
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
parallelism: getEnvironmentParallelism(),
|
||||
skeleton: SKELETON_FILE,
|
||||
});
|
||||
|
||||
|
||||
@@ -15,24 +15,27 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { buildBundle } from '../../lib/bundler';
|
||||
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
|
||||
import { getEnvironmentParallelism } from '../../lib/parallel';
|
||||
import { loadCliConfig } from '../../lib/config';
|
||||
import { paths } from '../../lib/paths';
|
||||
|
||||
interface BuildAppOptions {
|
||||
targetDir: string;
|
||||
writeStats: boolean;
|
||||
configPaths: string[];
|
||||
}
|
||||
|
||||
export async function buildApp(options: BuildAppOptions) {
|
||||
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
const { targetDir, writeStats, configPaths } = options;
|
||||
const { name } = await fs.readJson(resolvePath(targetDir, 'package.json'));
|
||||
await buildBundle({
|
||||
targetDir,
|
||||
entry: 'src/index',
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
statsJsonEnabled: options.writeStats,
|
||||
parallelism: getEnvironmentParallelism(),
|
||||
statsJsonEnabled: writeStats,
|
||||
...(await loadCliConfig({
|
||||
args: options.configPaths,
|
||||
args: configPaths,
|
||||
fromPackage: name,
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -19,20 +19,20 @@ import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import tar, { CreateOptions } from 'tar';
|
||||
import { createDistWorkspace } from '../../lib/packager';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
|
||||
import { getEnvironmentParallelism } from '../../lib/parallel';
|
||||
import { buildPackage, Output } from '../../lib/builder';
|
||||
|
||||
const BUNDLE_FILE = 'bundle.tar.gz';
|
||||
const SKELETON_FILE = 'skeleton.tar.gz';
|
||||
|
||||
interface BuildBackendOptions {
|
||||
targetDir: string;
|
||||
skipBuildDependencies: boolean;
|
||||
}
|
||||
|
||||
export async function buildBackend(options: BuildBackendOptions) {
|
||||
const targetDir = paths.resolveTarget('dist');
|
||||
const pkg = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
const { targetDir, skipBuildDependencies } = options;
|
||||
const pkg = await fs.readJson(resolvePath(targetDir, 'package.json'));
|
||||
|
||||
// We build the target package without generating type declarations.
|
||||
await buildPackage({ outputs: new Set([Output.cjs]) });
|
||||
@@ -41,9 +41,9 @@ export async function buildBackend(options: BuildBackendOptions) {
|
||||
try {
|
||||
await createDistWorkspace([pkg.name], {
|
||||
targetDir: tmpDir,
|
||||
buildDependencies: !options.skipBuildDependencies,
|
||||
buildDependencies: !skipBuildDependencies,
|
||||
buildExcludes: [pkg.name],
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
parallelism: getEnvironmentParallelism(),
|
||||
skeleton: SKELETON_FILE,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Command } from 'commander';
|
||||
import { buildPackage, Output } from '../../lib/builder';
|
||||
import { findRoleFromCommand, getRoleInfo } from '../../lib/role';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { buildApp } from './buildApp';
|
||||
import { buildBackend } from './buildBackend';
|
||||
|
||||
@@ -25,12 +26,14 @@ export async function command(cmd: Command): Promise<void> {
|
||||
|
||||
if (role === 'app') {
|
||||
return buildApp({
|
||||
targetDir: paths.resolveTarget('dist'),
|
||||
configPaths: cmd.config as string[],
|
||||
writeStats: Boolean(cmd.stats),
|
||||
});
|
||||
}
|
||||
if (role === 'backend') {
|
||||
return buildBackend({
|
||||
targetDir: paths.resolveTarget('dist'),
|
||||
skipBuildDependencies: Boolean(cmd.skipBuildDependencies),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,25 @@ const configOption = [
|
||||
Array<string>(),
|
||||
] as const;
|
||||
|
||||
export function registerRepoCommand(program: CommanderStatic) {
|
||||
const command = program
|
||||
.command('repo [command]', { hidden: true })
|
||||
.description(
|
||||
'Command that run across an entire Backstage project [EXPERIMENTAL]',
|
||||
);
|
||||
|
||||
command
|
||||
.command('build')
|
||||
.description(
|
||||
'Build packages in the project, excluding bundled app and backend packages.',
|
||||
)
|
||||
.option(
|
||||
'--all',
|
||||
'Build all packages, including bundled app and backend packages.',
|
||||
)
|
||||
.action(lazy(() => import('./repo/build').then(m => m.command)));
|
||||
}
|
||||
|
||||
export function registerScriptCommand(program: CommanderStatic) {
|
||||
const command = program
|
||||
.command('script [command]', { hidden: true })
|
||||
@@ -312,6 +331,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.description('Print configuration schema')
|
||||
.action(lazy(() => import('./config/schema').then(m => m.default)));
|
||||
|
||||
registerRepoCommand(program);
|
||||
registerScriptCommand(program);
|
||||
registerMigrateCommand(program);
|
||||
|
||||
|
||||
@@ -15,19 +15,34 @@
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { run } from '../lib/run';
|
||||
import { paths } from '../lib/paths';
|
||||
import { ESLint } from 'eslint';
|
||||
|
||||
export default async (cmd: Command, cmdArgs: string[]) => {
|
||||
const args = [
|
||||
'--ext=js,jsx,ts,tsx,mjs,cjs',
|
||||
'--max-warnings=0',
|
||||
`--format=${cmd.format}`,
|
||||
...(cmdArgs ?? [paths.targetDir]),
|
||||
];
|
||||
const eslint = new ESLint({
|
||||
cwd: paths.targetDir,
|
||||
fix: cmd.fix,
|
||||
extensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'],
|
||||
});
|
||||
|
||||
const results = await eslint.lintFiles(cmdArgs ?? ['.']);
|
||||
|
||||
if (cmd.fix) {
|
||||
args.push('--fix');
|
||||
await ESLint.outputFixes(results);
|
||||
}
|
||||
|
||||
await run('eslint', args);
|
||||
const formatter = await eslint.loadFormatter(cmd.format);
|
||||
|
||||
// This formatter uses the cwd to format file paths, so let's have that happen from the root instead
|
||||
if (cmd.format === 'eslint-formatter-friendly') {
|
||||
process.chdir(paths.targetRoot);
|
||||
}
|
||||
const resultText = formatter.format(results);
|
||||
|
||||
// If there is any feedback at all, we treat it as a lint failure. This should be
|
||||
// consistent with our old behavior of passing `--max-warnings=0` when invoking eslint.
|
||||
if (resultText) {
|
||||
console.log(resultText);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 chalk from 'chalk';
|
||||
import { Command } from 'commander';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { buildPackages, getOutputsForRole } from '../../lib/builder';
|
||||
import { PackageGraph } from '../../lib/monorepo';
|
||||
import { ExtendedPackage } from '../../lib/monorepo/PackageGraph';
|
||||
import { runParallelWorkers } from '../../lib/parallel';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { detectRoleFromPackage } from '../../lib/role';
|
||||
import { buildApp } from '../build/buildApp';
|
||||
import { buildBackend } from '../build/buildBackend';
|
||||
|
||||
function createScriptOptionsParser(anyCmd: Command, commandPath: string[]) {
|
||||
// Regardless of what command instance is passed in we want to find
|
||||
// the root command and resolve the path from there
|
||||
let rootCmd = anyCmd;
|
||||
while (rootCmd.parent) {
|
||||
rootCmd = rootCmd.parent;
|
||||
}
|
||||
|
||||
// Now find the command that was requested
|
||||
let targetCmd = rootCmd as Command | undefined;
|
||||
for (const name of commandPath) {
|
||||
targetCmd = targetCmd?.commands.find(c => c.name() === name) as
|
||||
| Command
|
||||
| undefined;
|
||||
}
|
||||
|
||||
if (!targetCmd) {
|
||||
throw new Error(`Could not find script command '${commandPath.join(' ')}'`);
|
||||
}
|
||||
const cmd = targetCmd;
|
||||
|
||||
const expectedScript = `backstage-cli ${commandPath.join(' ')}`;
|
||||
|
||||
return (scriptStr?: string) => {
|
||||
if (!scriptStr || !scriptStr.startsWith(expectedScript)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const argsStr = scriptStr.slice(expectedScript.length).trim();
|
||||
|
||||
// Can't clone or copy or even use commands as prototype, so we mutate
|
||||
// the necessary members instead, and then reset them once we're done
|
||||
const currentOpts = cmd._optionValues;
|
||||
const currentStore = cmd._storeOptionsAsProperties;
|
||||
|
||||
const result: Record<string, any> = {};
|
||||
cmd._storeOptionsAsProperties = false;
|
||||
cmd._optionValues = result;
|
||||
|
||||
// Triggers the writing of options to the result object
|
||||
cmd.parseOptions(argsStr.split(' '));
|
||||
|
||||
cmd._storeOptionsAsProperties = currentOpts;
|
||||
cmd._optionValues = currentStore;
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export async function command(cmd: Command): Promise<void> {
|
||||
const packages = await PackageGraph.listTargetPackages();
|
||||
const apps = new Array<ExtendedPackage>();
|
||||
const backends = new Array<ExtendedPackage>();
|
||||
|
||||
const parseBuildScript = createScriptOptionsParser(cmd, ['script', 'build']);
|
||||
|
||||
const options = packages.flatMap(pkg => {
|
||||
const role =
|
||||
pkg.packageJson.backstage?.role ?? detectRoleFromPackage(pkg.packageJson);
|
||||
if (!role) {
|
||||
console.warn(`Ignored ${pkg.packageJson.name} because it has no role`);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (role === 'app') {
|
||||
apps.push(pkg);
|
||||
return [];
|
||||
} else if (role === 'backend') {
|
||||
backends.push(pkg);
|
||||
return [];
|
||||
}
|
||||
|
||||
const outputs = getOutputsForRole(role);
|
||||
if (outputs.size === 0) {
|
||||
console.warn(`Ignored ${pkg.packageJson.name} because it has no output`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const buildOptions = parseBuildScript(pkg.packageJson.scripts?.build);
|
||||
if (!buildOptions) {
|
||||
console.warn(
|
||||
`Ignored ${pkg.packageJson.name} because it does not have a matching build script`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
targetDir: pkg.dir,
|
||||
outputs,
|
||||
logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `,
|
||||
minify: buildOptions.minify,
|
||||
useApiExtractor: buildOptions.experimentalTypeBuild,
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Building packages');
|
||||
await buildPackages(options);
|
||||
|
||||
if (cmd.all) {
|
||||
console.log('Building apps');
|
||||
await runParallelWorkers({
|
||||
items: apps,
|
||||
parallelismFactor: 1 / 2,
|
||||
worker: async pkg => {
|
||||
const buildOptions = parseBuildScript(pkg.packageJson.scripts?.build);
|
||||
if (!buildOptions) {
|
||||
console.warn(
|
||||
`Ignored ${pkg.packageJson.name} because it does not have a matching build script`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await buildApp({
|
||||
targetDir: pkg.dir,
|
||||
configPaths: (buildOptions.config as string[]) ?? [],
|
||||
writeStats: Boolean(buildOptions.stats),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Building backends');
|
||||
await runParallelWorkers({
|
||||
items: backends,
|
||||
parallelismFactor: 1 / 2,
|
||||
worker: async pkg => {
|
||||
const buildOptions = parseBuildScript(pkg.packageJson.scripts?.build);
|
||||
if (!buildOptions) {
|
||||
console.warn(
|
||||
`Ignored ${pkg.packageJson.name} because it does not have a matching build script`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await buildBackend({
|
||||
targetDir: pkg.dir,
|
||||
skipBuildDependencies: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -127,8 +127,8 @@ describe('bump', () => {
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using default pattern glob @backstage/*',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core-api',
|
||||
'Some packages are outdated, updating',
|
||||
'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
|
||||
@@ -252,19 +252,19 @@ describe('bump', () => {
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using custom pattern glob @{backstage,backstage-extra}/*',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage-extra/custom-two',
|
||||
'Checking for updates of @backstage-extra/custom',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage-extra/custom',
|
||||
'Checking for updates of @backstage-extra/custom-two',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core-api',
|
||||
'Some packages are outdated, updating',
|
||||
'unlocking @backstage-extra/custom@^1.0.1 ~> 1.1.0',
|
||||
'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
|
||||
'unlocking @backstage-extra/custom@^1.0.1 ~> 1.1.0',
|
||||
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
|
||||
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
|
||||
'bumping @backstage-extra/custom-two in a to ^2.0.0',
|
||||
'bumping @backstage/theme in b to ^2.0.0',
|
||||
'bumping @backstage-extra/custom-two in b to ^2.0.0',
|
||||
'bumping @backstage/theme in b to ^2.0.0',
|
||||
'Running yarn install to install new versions',
|
||||
'⚠️ The following packages may have breaking changes:',
|
||||
' @backstage-extra/custom-two : 1.0.0 ~> 2.0.0',
|
||||
@@ -348,14 +348,14 @@ describe('bump', () => {
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using default pattern glob @backstage/*',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
'Package info not found, ignoring package @backstage/core',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
'Package info not found, ignoring package @backstage/core',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Package info not found, ignoring package @backstage/core',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
'All Backstage packages are up to date!',
|
||||
]);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from '../../lib/versioning';
|
||||
import { forbiddenDuplicatesFilter } from './lint';
|
||||
import { BACKSTAGE_JSON } from '@backstage/cli-common';
|
||||
import { runParallelWorkers } from '../../lib/parallel';
|
||||
|
||||
const DEP_TYPES = [
|
||||
'dependencies',
|
||||
@@ -68,67 +69,76 @@ export default async (cmd: Command) => {
|
||||
const versionBumps = new Map<string, PkgVersionInfo[]>();
|
||||
// Track package versions that we want to remove from yarn.lock in order to trigger a bump
|
||||
const unlocked = Array<{ name: string; range: string; target: string }>();
|
||||
await workerThreads(16, dependencyMap.entries(), async ([name, pkgs]) => {
|
||||
let target: string;
|
||||
try {
|
||||
target = await findTargetVersion(name);
|
||||
} catch (error) {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
console.log(`Package info not found, ignoring package ${name}`);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
for (const pkg of pkgs) {
|
||||
if (semver.satisfies(target, pkg.range)) {
|
||||
if (semver.minVersion(pkg.range)?.version !== target) {
|
||||
unlocked.push({ name, range: pkg.range, target });
|
||||
await runParallelWorkers({
|
||||
parallelismFactor: 4,
|
||||
items: dependencyMap.entries(),
|
||||
async worker([name, pkgs]) {
|
||||
let target: string;
|
||||
try {
|
||||
target = await findTargetVersion(name);
|
||||
} catch (error) {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
console.log(`Package info not found, ignoring package ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
throw error;
|
||||
}
|
||||
versionBumps.set(
|
||||
pkg.name,
|
||||
(versionBumps.get(pkg.name) ?? []).concat({
|
||||
name,
|
||||
location: pkg.location,
|
||||
range: `^${target}`, // TODO(Rugvip): Option to use something else than ^?
|
||||
target,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (const pkg of pkgs) {
|
||||
if (semver.satisfies(target, pkg.range)) {
|
||||
if (semver.minVersion(pkg.range)?.version !== target) {
|
||||
unlocked.push({ name, range: pkg.range, target });
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
versionBumps.set(
|
||||
pkg.name,
|
||||
(versionBumps.get(pkg.name) ?? []).concat({
|
||||
name,
|
||||
location: pkg.location,
|
||||
range: `^${target}`, // TODO(Rugvip): Option to use something else than ^?
|
||||
target,
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const filter = (name: string) => minimatch(name, pattern);
|
||||
|
||||
// Check for updates of transitive backstage dependencies
|
||||
await workerThreads(16, lockfile.keys(), async name => {
|
||||
// Only check @backstage packages and friends, we don't want this to do a full update of all deps
|
||||
if (!filter(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target: string;
|
||||
try {
|
||||
target = await findTargetVersion(name);
|
||||
} catch (error) {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
console.log(`Package info not found, ignoring package ${name}`);
|
||||
await runParallelWorkers({
|
||||
parallelismFactor: 4,
|
||||
items: lockfile.keys(),
|
||||
async worker(name) {
|
||||
// Only check @backstage packages and friends, we don't want this to do a full update of all deps
|
||||
if (!filter(name)) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
for (const entry of lockfile.get(name) ?? []) {
|
||||
// Ignore lockfile entries that don't satisfy the version range, since
|
||||
// these can't cause the package to be locked to an older version
|
||||
if (!semver.satisfies(target, entry.range)) {
|
||||
continue;
|
||||
let target: string;
|
||||
try {
|
||||
target = await findTargetVersion(name);
|
||||
} catch (error) {
|
||||
if (isError(error) && error.name === 'NotFoundError') {
|
||||
console.log(`Package info not found, ignoring package ${name}`);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
// Unlock all entries that are within range but on the old version
|
||||
unlocked.push({ name, range: entry.range, target });
|
||||
}
|
||||
|
||||
for (const entry of lockfile.get(name) ?? []) {
|
||||
// Ignore lockfile entries that don't satisfy the version range, since
|
||||
// these can't cause the package to be locked to an older version
|
||||
if (!semver.satisfies(target, entry.range)) {
|
||||
continue;
|
||||
}
|
||||
// Unlock all entries that are within range but on the old version
|
||||
unlocked.push({ name, range: entry.range, target });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log();
|
||||
@@ -163,38 +173,42 @@ export default async (cmd: Command) => {
|
||||
}
|
||||
|
||||
const breakingUpdates = new Map<string, { from: string; to: string }>();
|
||||
await workerThreads(16, versionBumps.entries(), async ([name, deps]) => {
|
||||
const pkgPath = resolvePath(deps[0].location, 'package.json');
|
||||
const pkgJson = await fs.readJson(pkgPath);
|
||||
await runParallelWorkers({
|
||||
parallelismFactor: 4,
|
||||
items: versionBumps.entries(),
|
||||
async worker([name, deps]) {
|
||||
const pkgPath = resolvePath(deps[0].location, 'package.json');
|
||||
const pkgJson = await fs.readJson(pkgPath);
|
||||
|
||||
for (const dep of deps) {
|
||||
console.log(
|
||||
`${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan(
|
||||
name,
|
||||
)} to ${chalk.yellow(dep.range)}`,
|
||||
);
|
||||
for (const dep of deps) {
|
||||
console.log(
|
||||
`${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan(
|
||||
name,
|
||||
)} to ${chalk.yellow(dep.range)}`,
|
||||
);
|
||||
|
||||
for (const depType of DEP_TYPES) {
|
||||
if (depType in pkgJson && dep.name in pkgJson[depType]) {
|
||||
const oldRange = pkgJson[depType][dep.name];
|
||||
pkgJson[depType][dep.name] = dep.range;
|
||||
for (const depType of DEP_TYPES) {
|
||||
if (depType in pkgJson && dep.name in pkgJson[depType]) {
|
||||
const oldRange = pkgJson[depType][dep.name];
|
||||
pkgJson[depType][dep.name] = dep.range;
|
||||
|
||||
// Check if the update was at least a pre-v1 minor or post-v1 major release
|
||||
const lockfileEntry = lockfile
|
||||
.get(dep.name)
|
||||
?.find(entry => entry.range === oldRange);
|
||||
if (lockfileEntry) {
|
||||
const from = lockfileEntry.version;
|
||||
const to = dep.target;
|
||||
if (!semver.satisfies(to, `^${from}`)) {
|
||||
breakingUpdates.set(dep.name, { from, to });
|
||||
// Check if the update was at least a pre-v1 minor or post-v1 major release
|
||||
const lockfileEntry = lockfile
|
||||
.get(dep.name)
|
||||
?.find(entry => entry.range === oldRange);
|
||||
if (lockfileEntry) {
|
||||
const from = lockfileEntry.version;
|
||||
const to = dep.target;
|
||||
if (!semver.satisfies(to, `^${from}`)) {
|
||||
breakingUpdates.set(dep.name, { from, to });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeJson(pkgPath, pkgJson, { spaces: 2 });
|
||||
await fs.writeJson(pkgPath, pkgJson, { spaces: 2 });
|
||||
},
|
||||
});
|
||||
|
||||
console.log();
|
||||
@@ -324,27 +338,3 @@ export async function bumpBackstageJsonVersion() {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function workerThreads<T>(
|
||||
count: number,
|
||||
items: IterableIterator<T>,
|
||||
fn: (item: T) => Promise<void>,
|
||||
) {
|
||||
const queue = Array.from(items);
|
||||
|
||||
async function pop() {
|
||||
const item = queue.pop();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
await fn(item);
|
||||
await pop();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => pop()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,146 +16,127 @@
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
relative as relativePath,
|
||||
resolve as resolvePath,
|
||||
dirname,
|
||||
} from 'path';
|
||||
import { Worker } from 'worker_threads';
|
||||
import { relative as relativePath, resolve as resolvePath } from 'path';
|
||||
import { paths } from '../paths';
|
||||
import { buildTypeDefinitionsWorker } from './buildTypeDefinitionsWorker';
|
||||
|
||||
// These message types are ignored since we want to avoid duplicating the logic of
|
||||
// handling them correctly, and we already have the API Reports warning about them.
|
||||
const ignoredMessages = new Set(['tsdoc-undefined-tag', 'ae-forgotten-export']);
|
||||
|
||||
let apiExtractor: undefined | typeof import('@microsoft/api-extractor');
|
||||
function prepareApiExtractor() {
|
||||
if (apiExtractor) {
|
||||
return apiExtractor;
|
||||
}
|
||||
|
||||
try {
|
||||
apiExtractor = require('@microsoft/api-extractor');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
'Failed to resolve @microsoft/api-extractor, it must best installed ' +
|
||||
'as a dependency of your project in order to use experimental type builds',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* All of this monkey patching below is because MUI has these bare package.json file as a method
|
||||
* for making TypeScript accept imports like `@material-ui/core/Button`, and improve tree-shaking
|
||||
* by declaring them side effect free.
|
||||
*
|
||||
* The package.json lookup logic in api-extractor really doesn't like that though, as it enforces
|
||||
* that the 'name' field exists in all package.json files that it discovers. This below is just
|
||||
* making sure that we ignore those file package.json files instead of crashing.
|
||||
*/
|
||||
const {
|
||||
PackageJsonLookup,
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
} = require('@rushstack/node-core-library/lib/PackageJsonLookup');
|
||||
|
||||
const old = PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor;
|
||||
PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor =
|
||||
function tryGetPackageJsonFilePathForPatch(path: string) {
|
||||
if (
|
||||
path.includes('@material-ui') &&
|
||||
!dirname(path).endsWith('@material-ui')
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return old.call(this, path);
|
||||
};
|
||||
|
||||
return apiExtractor!;
|
||||
}
|
||||
|
||||
export async function buildTypeDefinitions() {
|
||||
const { Extractor, ExtractorConfig } = prepareApiExtractor();
|
||||
|
||||
const distTypesPackageDir = paths.resolveTargetRoot(
|
||||
'dist-types',
|
||||
relativePath(paths.targetRoot, paths.targetDir),
|
||||
export async function buildTypeDefinitions(
|
||||
targetDirs: string[] = [paths.targetDir],
|
||||
) {
|
||||
const packageDirs = targetDirs.map(dir =>
|
||||
relativePath(paths.targetRoot, dir),
|
||||
);
|
||||
const entryPoint = resolvePath(distTypesPackageDir, 'src/index.d.ts');
|
||||
const entryPoints = await Promise.all(
|
||||
packageDirs.map(async dir => {
|
||||
const entryPoint = paths.resolveTargetRoot(
|
||||
'dist-types',
|
||||
dir,
|
||||
'src/index.d.ts',
|
||||
);
|
||||
|
||||
const declarationsExist = await fs.pathExists(entryPoint);
|
||||
if (!declarationsExist) {
|
||||
const path = relativePath(paths.targetDir, entryPoint);
|
||||
throw new Error(
|
||||
`No declaration files found at ${path}, be sure to run ${chalk.bgRed.white(
|
||||
'yarn tsc',
|
||||
)} to generate .d.ts files before packaging`,
|
||||
);
|
||||
}
|
||||
const declarationsExist = await fs.pathExists(entryPoint);
|
||||
if (!declarationsExist) {
|
||||
throw new Error(
|
||||
`No declaration files found at ${entryPoint}, be sure to run ${chalk.bgRed.white(
|
||||
'yarn tsc',
|
||||
)} to generate .d.ts files before packaging`,
|
||||
);
|
||||
}
|
||||
return entryPoint;
|
||||
}),
|
||||
);
|
||||
|
||||
const extractorConfig = ExtractorConfig.prepare({
|
||||
configObject: {
|
||||
mainEntryPointFilePath: entryPoint,
|
||||
bundledPackages: [],
|
||||
const workerConfigs = packageDirs.map(packageDir => {
|
||||
const targetDir = paths.resolveTargetRoot(packageDir);
|
||||
const targetTypesDir = paths.resolveTargetRoot('dist-types', packageDir);
|
||||
const extractorOptions = {
|
||||
configObject: {
|
||||
mainEntryPointFilePath: resolvePath(targetTypesDir, 'src/index.d.ts'),
|
||||
bundledPackages: [],
|
||||
|
||||
compiler: {
|
||||
skipLibCheck: true,
|
||||
tsconfigFilePath: paths.resolveTargetRoot('tsconfig.json'),
|
||||
compiler: {
|
||||
skipLibCheck: true,
|
||||
tsconfigFilePath: paths.resolveTargetRoot('tsconfig.json'),
|
||||
},
|
||||
|
||||
dtsRollup: {
|
||||
enabled: true,
|
||||
untrimmedFilePath: resolvePath(targetDir, 'dist/index.alpha.d.ts'),
|
||||
betaTrimmedFilePath: resolvePath(targetDir, 'dist/index.beta.d.ts'),
|
||||
publicTrimmedFilePath: resolvePath(targetDir, 'dist/index.d.ts'),
|
||||
},
|
||||
|
||||
newlineKind: 'lf',
|
||||
|
||||
projectFolder: targetDir,
|
||||
},
|
||||
|
||||
dtsRollup: {
|
||||
enabled: true,
|
||||
untrimmedFilePath: paths.resolveTarget('dist/index.alpha.d.ts'),
|
||||
betaTrimmedFilePath: paths.resolveTarget('dist/index.beta.d.ts'),
|
||||
publicTrimmedFilePath: paths.resolveTarget('dist/index.d.ts'),
|
||||
},
|
||||
|
||||
newlineKind: 'lf',
|
||||
|
||||
projectFolder: paths.targetDir,
|
||||
},
|
||||
configObjectFullPath: paths.targetDir,
|
||||
packageJsonFullPath: paths.resolveTarget('package.json'),
|
||||
configObjectFullPath: targetDir,
|
||||
packageJsonFullPath: resolvePath(targetDir, 'package.json'),
|
||||
};
|
||||
return { extractorOptions, targetTypesDir };
|
||||
});
|
||||
|
||||
const typescriptDir = paths.resolveTargetRoot('node_modules/typescript');
|
||||
const hasTypescript = await fs.pathExists(typescriptDir);
|
||||
const extractorResult = Extractor.invoke(extractorConfig, {
|
||||
typescriptCompilerFolder: hasTypescript ? typescriptDir : undefined,
|
||||
localBuild: false,
|
||||
showVerboseMessages: false,
|
||||
showDiagnostics: false,
|
||||
messageCallback(message) {
|
||||
message.handled = true;
|
||||
if (ignoredMessages.has(message.messageId)) {
|
||||
return;
|
||||
}
|
||||
const typescriptCompilerFolder = hasTypescript ? typescriptDir : undefined;
|
||||
|
||||
let text = `${message.text} (${message.messageId})`;
|
||||
if (message.sourceFilePath) {
|
||||
text += ' at ';
|
||||
text += relativePath(distTypesPackageDir, message.sourceFilePath);
|
||||
if (message.sourceFileLine) {
|
||||
text += `:${message.sourceFileLine}`;
|
||||
if (message.sourceFileColumn) {
|
||||
text += `:${message.sourceFileColumn}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.logLevel === 'error') {
|
||||
console.error(chalk.red(`Error: ${text}`));
|
||||
} else if (
|
||||
message.logLevel === 'warning' ||
|
||||
message.category === 'Extractor'
|
||||
) {
|
||||
console.warn(`Warning: ${text}`);
|
||||
} else {
|
||||
console.log(text);
|
||||
}
|
||||
const worker = new Worker(`(${buildTypeDefinitionsWorker})()`, {
|
||||
eval: true,
|
||||
workerData: {
|
||||
entryPoints,
|
||||
workerConfigs,
|
||||
typescriptCompilerFolder,
|
||||
},
|
||||
});
|
||||
|
||||
if (!extractorResult.succeeded) {
|
||||
throw new Error(
|
||||
`Type definition build completed with ${extractorResult.errorCount} errors` +
|
||||
` and ${extractorResult.warningCount} warnings`,
|
||||
);
|
||||
}
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
worker.once('error', reject);
|
||||
worker.once('exit', code => {
|
||||
if (code) {
|
||||
reject(new Error(`Worker exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
worker.on('message', data => {
|
||||
if (data.type === 'done') {
|
||||
if (data.error) {
|
||||
reject(data.error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
} else if (data.type === 'message') {
|
||||
const { message, targetTypesDir } = data;
|
||||
|
||||
if (ignoredMessages.has(message.messageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let text = `${message.text} (${message.messageId})`;
|
||||
if (message.sourceFilePath) {
|
||||
text += ' at ';
|
||||
text += relativePath(targetTypesDir, message.sourceFilePath);
|
||||
if (message.sourceFileLine) {
|
||||
text += `:${message.sourceFileLine}`;
|
||||
if (message.sourceFileColumn) {
|
||||
text += `:${message.sourceFileColumn}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.logLevel === 'error') {
|
||||
console.error(chalk.red(`Error: ${text}`));
|
||||
} else if (
|
||||
message.logLevel === 'warning' ||
|
||||
message.category === 'Extractor'
|
||||
) {
|
||||
console.warn(`Warning: ${text}`);
|
||||
} else {
|
||||
console.log(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: This is a worker thread function that is stringified and executed
|
||||
* withing a `worker_threads.Worker`. Everything in this function must
|
||||
* be self-contained.
|
||||
* Using TypeScript is fine as it is transpiled before being stringified.
|
||||
*/
|
||||
export function buildTypeDefinitionsWorker() {
|
||||
try {
|
||||
require('@microsoft/api-extractor');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
'Failed to resolve @microsoft/api-extractor, it must best installed ' +
|
||||
'as a dependency of your project in order to use experimental type builds',
|
||||
);
|
||||
}
|
||||
|
||||
const { dirname } = require('path');
|
||||
const { workerData, parentPort } = require('worker_threads');
|
||||
const { entryPoints, workerConfigs, typescriptCompilerFolder } = workerData;
|
||||
|
||||
const apiExtractor = require('@microsoft/api-extractor');
|
||||
const { Extractor, ExtractorConfig, CompilerState } = apiExtractor;
|
||||
|
||||
/**
|
||||
* All of this monkey patching below is because MUI has these bare package.json file as a method
|
||||
* for making TypeScript accept imports like `@material-ui/core/Button`, and improve tree-shaking
|
||||
* by declaring them side effect free.
|
||||
*
|
||||
* The package.json lookup logic in api-extractor really doesn't like that though, as it enforces
|
||||
* that the 'name' field exists in all package.json files that it discovers. This below is just
|
||||
* making sure that we ignore those file package.json files instead of crashing.
|
||||
*/
|
||||
const {
|
||||
PackageJsonLookup,
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
} = require('@rushstack/node-core-library/lib/PackageJsonLookup');
|
||||
|
||||
const old = PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor;
|
||||
PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor =
|
||||
function tryGetPackageJsonFilePathForPatch(path: string) {
|
||||
if (
|
||||
path.includes('@material-ui') &&
|
||||
!dirname(path).endsWith('@material-ui')
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return old.call(this, path);
|
||||
};
|
||||
|
||||
let success = true;
|
||||
let compilerState;
|
||||
for (const { extractorOptions, targetTypesDir } of workerConfigs) {
|
||||
const extractorConfig = ExtractorConfig.prepare(extractorOptions);
|
||||
|
||||
if (!compilerState) {
|
||||
compilerState = CompilerState.create(extractorConfig, {
|
||||
additionalEntryPoints: entryPoints,
|
||||
});
|
||||
}
|
||||
|
||||
const extractorResult = Extractor.invoke(extractorConfig, {
|
||||
compilerState,
|
||||
localBuild: false,
|
||||
typescriptCompilerFolder,
|
||||
showVerboseMessages: false,
|
||||
showDiagnostics: false,
|
||||
messageCallback: (message: any) => {
|
||||
message.handled = true;
|
||||
parentPort.postMessage({ type: 'message', message, targetTypesDir });
|
||||
},
|
||||
});
|
||||
|
||||
if (!extractorResult.succeeded) {
|
||||
parentPort.postMessage({
|
||||
type: 'done',
|
||||
error: new Error(
|
||||
`Type definition build completed with ${extractorResult.errorCount} errors` +
|
||||
` and ${extractorResult.warningCount} warnings`,
|
||||
),
|
||||
});
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
parentPort.postMessage({ type: 'done' });
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { relative as relativePath, resolve as resolvePath } from 'path';
|
||||
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
@@ -26,7 +26,7 @@ import svgr from '@svgr/rollup';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import json from '@rollup/plugin-json';
|
||||
import yaml from '@rollup/plugin-yaml';
|
||||
import { RollupOptions, OutputOptions } from 'rollup';
|
||||
import { RollupOptions, OutputOptions, RollupWarning } from 'rollup';
|
||||
|
||||
import { forwardFileImports } from './plugins';
|
||||
import { BuildOptions, Output } from './types';
|
||||
@@ -37,6 +37,19 @@ export async function makeRollupConfigs(
|
||||
options: BuildOptions,
|
||||
): Promise<RollupOptions[]> {
|
||||
const configs = new Array<RollupOptions>();
|
||||
const targetDir = options.targetDir ?? paths.targetDir;
|
||||
const onwarn = ({ code, message }: RollupWarning) => {
|
||||
if (code === 'EMPTY_BUNDLE') {
|
||||
return; // We don't care about this one
|
||||
}
|
||||
if (options.logPrefix) {
|
||||
console.log(options.logPrefix + message);
|
||||
} else {
|
||||
console.log(message);
|
||||
}
|
||||
};
|
||||
|
||||
const distDir = resolvePath(targetDir, 'dist');
|
||||
|
||||
if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) {
|
||||
const output = new Array<OutputOptions>();
|
||||
@@ -44,7 +57,7 @@ export async function makeRollupConfigs(
|
||||
|
||||
if (options.outputs.has(Output.cjs)) {
|
||||
output.push({
|
||||
dir: 'dist',
|
||||
dir: distDir,
|
||||
entryFileNames: 'index.cjs.js',
|
||||
chunkFileNames: 'cjs/[name]-[hash].cjs.js',
|
||||
format: 'commonjs',
|
||||
@@ -53,7 +66,7 @@ export async function makeRollupConfigs(
|
||||
}
|
||||
if (options.outputs.has(Output.esm)) {
|
||||
output.push({
|
||||
dir: 'dist',
|
||||
dir: distDir,
|
||||
entryFileNames: 'index.esm.js',
|
||||
chunkFileNames: 'esm/[name]-[hash].esm.js',
|
||||
format: 'module',
|
||||
@@ -64,12 +77,14 @@ export async function makeRollupConfigs(
|
||||
}
|
||||
|
||||
configs.push({
|
||||
input: 'src/index.ts',
|
||||
input: resolvePath(targetDir, 'src/index.ts'),
|
||||
output,
|
||||
onwarn,
|
||||
preserveEntrySignatures: 'strict',
|
||||
external: require('module').builtinModules,
|
||||
plugins: [
|
||||
peerDepsExternal({
|
||||
packageJsonPath: resolvePath(targetDir, 'package.json'),
|
||||
includeDependencies: true,
|
||||
}),
|
||||
resolve({ mainFields }),
|
||||
@@ -109,13 +124,13 @@ export async function makeRollupConfigs(
|
||||
if (options.outputs.has(Output.types) && !options.useApiExtractor) {
|
||||
const typesInput = paths.resolveTargetRoot(
|
||||
'dist-types',
|
||||
relativePath(paths.targetRoot, paths.targetDir),
|
||||
relativePath(paths.targetRoot, targetDir),
|
||||
'src/index.d.ts',
|
||||
);
|
||||
|
||||
const declarationsExist = await fs.pathExists(typesInput);
|
||||
if (!declarationsExist) {
|
||||
const path = relativePath(paths.targetDir, typesInput);
|
||||
const path = relativePath(targetDir, typesInput);
|
||||
throw new Error(
|
||||
`No declaration files found at ${path}, be sure to run ${chalk.bgRed.white(
|
||||
'yarn tsc',
|
||||
@@ -126,9 +141,10 @@ export async function makeRollupConfigs(
|
||||
configs.push({
|
||||
input: typesInput,
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
file: resolvePath(distDir, 'index.d.ts'),
|
||||
format: 'es',
|
||||
},
|
||||
onwarn,
|
||||
plugins: [dts()],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { buildPackage } from './packager';
|
||||
export { buildPackage, buildPackages, getOutputsForRole } from './packager';
|
||||
export { Output } from './types';
|
||||
export type { BuildOptions } from './types';
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
import fs from 'fs-extra';
|
||||
import { rollup, RollupOptions } from 'rollup';
|
||||
import chalk from 'chalk';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { relative as relativePath, resolve as resolvePath } from 'path';
|
||||
import { paths } from '../paths';
|
||||
import { makeRollupConfigs } from './config';
|
||||
import { BuildOptions, Output } from './types';
|
||||
import { buildTypeDefinitions } from './buildTypeDefinitions';
|
||||
import { getRoleInfo } from '../role';
|
||||
import { runParallelWorkers } from '../parallel';
|
||||
|
||||
export function formatErrorMessage(error: any) {
|
||||
let msg = '';
|
||||
@@ -116,3 +118,51 @@ export const buildPackage = async (options: BuildOptions) => {
|
||||
|
||||
await Promise.all(buildTasks);
|
||||
};
|
||||
|
||||
export const buildPackages = async (options: BuildOptions[]) => {
|
||||
if (options.some(opt => !opt.targetDir)) {
|
||||
throw new Error('targetDir must be set for all build options');
|
||||
}
|
||||
const rollupConfigs = await Promise.all(options.map(makeRollupConfigs));
|
||||
|
||||
await Promise.all(
|
||||
options.map(({ targetDir }) => fs.remove(resolvePath(targetDir!, 'dist'))),
|
||||
);
|
||||
|
||||
const buildTasks = rollupConfigs.flat().map(opts => () => rollupBuild(opts));
|
||||
|
||||
const typeDefinitionTargetDirs = options
|
||||
.filter(
|
||||
({ outputs, useApiExtractor }) =>
|
||||
outputs.has(Output.types) && useApiExtractor,
|
||||
)
|
||||
.map(_ => _.targetDir!);
|
||||
|
||||
if (typeDefinitionTargetDirs.length > 0) {
|
||||
// Make sure this one is started first
|
||||
buildTasks.unshift(() => buildTypeDefinitions(typeDefinitionTargetDirs));
|
||||
}
|
||||
|
||||
await runParallelWorkers({
|
||||
items: buildTasks,
|
||||
worker: async task => task(),
|
||||
});
|
||||
};
|
||||
|
||||
export function getOutputsForRole(role: string): Set<Output> {
|
||||
const outputs = new Set<Output>();
|
||||
|
||||
for (const output of getRoleInfo(role).output) {
|
||||
if (output === 'cjs') {
|
||||
outputs.add(Output.cjs);
|
||||
}
|
||||
if (output === 'esm') {
|
||||
outputs.add(Output.esm);
|
||||
}
|
||||
if (output === 'types') {
|
||||
outputs.add(Output.types);
|
||||
}
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ export enum Output {
|
||||
}
|
||||
|
||||
export type BuildOptions = {
|
||||
logPrefix?: string;
|
||||
targetDir?: string;
|
||||
outputs: Set<Output>;
|
||||
minify?: boolean;
|
||||
useApiExtractor?: boolean;
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function createConfig(
|
||||
if (checksEnabled) {
|
||||
plugins.push(
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: { configFile: paths.targetTsConfig },
|
||||
typescript: { configFile: paths.targetTsConfig, memoryLimit: 4096 },
|
||||
}),
|
||||
new ESLintPlugin({
|
||||
context: paths.targetPath,
|
||||
|
||||
@@ -15,55 +15,58 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { paths } from '../paths';
|
||||
|
||||
export type BundlingPathsOptions = {
|
||||
// bundle entrypoint, e.g. 'src/index'
|
||||
entry: string;
|
||||
// Target directory, defaulting to paths.targetDir
|
||||
targetDir?: string;
|
||||
};
|
||||
|
||||
export function resolveBundlingPaths(options: BundlingPathsOptions) {
|
||||
const { entry } = options;
|
||||
const { entry, targetDir = paths.targetDir } = options;
|
||||
|
||||
const resolveTargetModule = (pathString: string) => {
|
||||
for (const ext of ['mjs', 'js', 'ts', 'tsx', 'jsx']) {
|
||||
const filePath = paths.resolveTarget(`${pathString}.${ext}`);
|
||||
const filePath = resolvePath(targetDir, `${pathString}.${ext}`);
|
||||
if (fs.pathExistsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return paths.resolveTarget(`${pathString}.js`);
|
||||
return resolvePath(targetDir, `${pathString}.js`);
|
||||
};
|
||||
|
||||
let targetPublic = undefined;
|
||||
let targetHtml = paths.resolveTarget('public/index.html');
|
||||
let targetHtml = resolvePath(targetDir, 'public/index.html');
|
||||
|
||||
// Prefer public folder
|
||||
if (fs.pathExistsSync(targetHtml)) {
|
||||
targetPublic = paths.resolveTarget('public');
|
||||
targetPublic = resolvePath(targetDir, 'public');
|
||||
} else {
|
||||
targetHtml = paths.resolveTarget(`${entry}.html`);
|
||||
targetHtml = resolvePath(targetDir, `${entry}.html`);
|
||||
if (!fs.pathExistsSync(targetHtml)) {
|
||||
targetHtml = paths.resolveOwn('templates/serve_index.html');
|
||||
}
|
||||
}
|
||||
|
||||
// Backend plugin dev run file
|
||||
const targetRunFile = paths.resolveTarget('src/run.ts');
|
||||
const targetRunFile = resolvePath(targetDir, 'src/run.ts');
|
||||
const runFileExists = fs.pathExistsSync(targetRunFile);
|
||||
|
||||
return {
|
||||
targetHtml,
|
||||
targetPublic,
|
||||
targetPath: paths.resolveTarget('.'),
|
||||
targetPath: resolvePath(targetDir, '.'),
|
||||
targetRunFile: runFileExists ? targetRunFile : undefined,
|
||||
targetDist: paths.resolveTarget('dist'),
|
||||
targetAssets: paths.resolveTarget('assets'),
|
||||
targetSrc: paths.resolveTarget('src'),
|
||||
targetDev: paths.resolveTarget('dev'),
|
||||
targetDist: resolvePath(targetDir, 'dist'),
|
||||
targetAssets: resolvePath(targetDir, 'assets'),
|
||||
targetSrc: resolvePath(targetDir, 'src'),
|
||||
targetDev: resolvePath(targetDir, 'dev'),
|
||||
targetEntry: resolveTargetModule(entry),
|
||||
targetTsConfig: paths.resolveTargetRoot('tsconfig.json'),
|
||||
targetPackageJson: paths.resolveTarget('package.json'),
|
||||
targetPackageJson: resolvePath(targetDir, 'package.json'),
|
||||
rootNodeModules: paths.resolveTargetRoot('node_modules'),
|
||||
root: paths.targetRoot,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import { AppConfig, Config } from '@backstage/config';
|
||||
import { BundlingPathsOptions } from './paths';
|
||||
import { ParallelOption } from '../parallel';
|
||||
import { ConfigSchema } from '@backstage/config-loader';
|
||||
|
||||
export type BundlingOptions = {
|
||||
@@ -25,7 +24,7 @@ export type BundlingOptions = {
|
||||
frontendConfig: Config;
|
||||
frontendAppConfigs: AppConfig[];
|
||||
baseUrl: URL;
|
||||
parallel?: ParallelOption;
|
||||
parallelism?: number;
|
||||
};
|
||||
|
||||
export type ServeOptions = BundlingPathsOptions & {
|
||||
@@ -35,8 +34,10 @@ export type ServeOptions = BundlingPathsOptions & {
|
||||
};
|
||||
|
||||
export type BuildOptions = BundlingPathsOptions & {
|
||||
// Target directory, defaulting to paths.targetDir
|
||||
targetDir?: string;
|
||||
statsJsonEnabled: boolean;
|
||||
parallel?: ParallelOption;
|
||||
parallelism?: number;
|
||||
schema?: ConfigSchema;
|
||||
frontendConfig: Config;
|
||||
frontendAppConfigs: AppConfig[];
|
||||
@@ -45,7 +46,7 @@ export type BuildOptions = BundlingPathsOptions & {
|
||||
export type BackendBundlingOptions = {
|
||||
checksEnabled: boolean;
|
||||
isDev: boolean;
|
||||
parallel?: ParallelOption;
|
||||
parallelism?: number;
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* 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 npmPackList from 'npm-packlist';
|
||||
import { join as joinPath, resolve as resolvePath } from 'path';
|
||||
|
||||
const SKIPPED_KEYS = ['access', 'registry', 'tag', 'alphaTypes', 'betaTypes'];
|
||||
|
||||
// Writes e.g. alpha/package.json
|
||||
async function writeReleaseStageEntrypoint(
|
||||
pkg: any,
|
||||
stage: 'alpha' | 'beta',
|
||||
targetDir: string,
|
||||
) {
|
||||
await fs.ensureDir(resolvePath(targetDir, stage));
|
||||
await fs.writeJson(
|
||||
resolvePath(targetDir, stage, 'package.json'),
|
||||
{
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
main: (pkg.publishConfig.main || pkg.main) && '..',
|
||||
module: (pkg.publishConfig.module || pkg.module) && '..',
|
||||
browser: (pkg.publishConfig.browser || pkg.browser) && '..',
|
||||
types: joinPath('..', pkg.publishConfig[`${stage}Types`]),
|
||||
},
|
||||
{ encoding: 'utf8', spaces: 2 },
|
||||
);
|
||||
}
|
||||
|
||||
export async function copyPackageDist(packageDir: string, targetDir: string) {
|
||||
const pkgPath = resolvePath(packageDir, 'package.json');
|
||||
const pkgContent = await fs.readFile(pkgPath, 'utf8');
|
||||
const pkg = JSON.parse(pkgContent);
|
||||
|
||||
const publishConfig = pkg.publishConfig ?? {};
|
||||
for (const key of Object.keys(publishConfig)) {
|
||||
if (!SKIPPED_KEYS.includes(key)) {
|
||||
pkg[key] = publishConfig[key];
|
||||
}
|
||||
}
|
||||
|
||||
// We remove the dependencies from package.json of packages that are marked
|
||||
// as bundled, so that yarn doesn't try to install them.
|
||||
if (pkg.bundled) {
|
||||
delete pkg.dependencies;
|
||||
delete pkg.devDependencies;
|
||||
delete pkg.peerDependencies;
|
||||
delete pkg.optionalDependencies;
|
||||
}
|
||||
|
||||
// Write the modified package.json so that the file listing is correct
|
||||
await fs.writeJson(pkgPath, pkg, { encoding: 'utf8', spaces: 2 });
|
||||
|
||||
// Lists all dist files, respecting .npmignore, files field in package.json, etc.
|
||||
const filePaths = await npmPackList({ path: packageDir });
|
||||
|
||||
await fs.ensureDir(targetDir);
|
||||
for (const filePath of filePaths.sort()) {
|
||||
await fs.copy(
|
||||
resolvePath(packageDir, filePath),
|
||||
resolvePath(targetDir, filePath),
|
||||
);
|
||||
}
|
||||
|
||||
if (publishConfig.alphaTypes) {
|
||||
await writeReleaseStageEntrypoint(pkg, 'alpha', targetDir);
|
||||
}
|
||||
if (publishConfig.betaTypes) {
|
||||
await writeReleaseStageEntrypoint(pkg, 'beta', targetDir);
|
||||
}
|
||||
|
||||
// Restore package.json
|
||||
await fs.writeFile(pkgPath, pkgContent, 'utf8');
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import {
|
||||
join as joinPath,
|
||||
resolve as resolvePath,
|
||||
relative as relativePath,
|
||||
} from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import tar, { CreateOptions } from 'tar';
|
||||
import partition from 'lodash/partition';
|
||||
import { paths } from '../paths';
|
||||
import { run } from '../run';
|
||||
import {
|
||||
dependencies as cliDependencies,
|
||||
devDependencies as cliDevDependencies,
|
||||
} from '../../../package.json';
|
||||
import { PackageGraph, PackageGraphNode } from '../monorepo';
|
||||
import {
|
||||
BuildOptions,
|
||||
buildPackages,
|
||||
getOutputsForRole,
|
||||
Output,
|
||||
} from '../builder';
|
||||
import { copyPackageDist } from './copyPackageDist';
|
||||
|
||||
// These packages aren't safe to pack in parallel since the CLI depends on them
|
||||
const UNSAFE_PACKAGES = [
|
||||
...Object.keys(cliDependencies),
|
||||
...Object.keys(cliDevDependencies),
|
||||
];
|
||||
|
||||
type FileEntry =
|
||||
| string
|
||||
| {
|
||||
src: string;
|
||||
dest: string;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
/**
|
||||
* Target directory for the dist workspace, defaults to a temporary directory
|
||||
*/
|
||||
targetDir?: string;
|
||||
|
||||
/**
|
||||
* Files to copy into the target workspace.
|
||||
*
|
||||
* Defaults to ['yarn.lock', 'package.json'].
|
||||
*/
|
||||
files?: FileEntry[];
|
||||
|
||||
/**
|
||||
* If set to true, the target packages are built before they are packaged into the workspace.
|
||||
*/
|
||||
buildDependencies?: boolean;
|
||||
|
||||
/**
|
||||
* When `buildDependencies` is set, this list of packages will not be built even if they are dependencies.
|
||||
*/
|
||||
buildExcludes?: string[];
|
||||
|
||||
/**
|
||||
* Controls amount of parallelism in some build steps.
|
||||
*/
|
||||
parallelism?: number;
|
||||
|
||||
/**
|
||||
* If set, creates a skeleton tarball that contains all package.json files
|
||||
* with the same structure as the workspace dir.
|
||||
*/
|
||||
skeleton?: 'skeleton.tar' | 'skeleton.tar.gz';
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses `yarn pack` to package local packages and unpacks them into a dist workspace.
|
||||
* The target workspace will end up containing dist version of each package and
|
||||
* will be suitable for packaging e.g. into a docker image.
|
||||
*
|
||||
* This creates a structure that is functionally similar to if the packages were
|
||||
* installed from npm, but uses Yarn workspaces to link to them at runtime.
|
||||
*/
|
||||
export async function createDistWorkspace(
|
||||
packageNames: string[],
|
||||
options: Options = {},
|
||||
) {
|
||||
const targetDir =
|
||||
options.targetDir ??
|
||||
(await fs.mkdtemp(resolvePath(tmpdir(), 'dist-workspace')));
|
||||
|
||||
const packages = await PackageGraph.listTargetPackages();
|
||||
const packageGraph = PackageGraph.fromPackages(packages);
|
||||
const targetNames = packageGraph.collectPackageNames(packageNames, node => {
|
||||
// Don't include dependencies of packages that are marked as bundled
|
||||
if (node.packageJson.bundled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return node.publishedLocalDependencies.keys();
|
||||
});
|
||||
const targets = Array.from(targetNames).map(name => packageGraph.get(name)!);
|
||||
|
||||
if (options.buildDependencies) {
|
||||
const exclude = options.buildExcludes ?? [];
|
||||
|
||||
const toBuild = new Set(
|
||||
targets.map(_ => _.name).filter(name => !exclude.includes(name)),
|
||||
);
|
||||
|
||||
const standardBuilds = new Array<BuildOptions>();
|
||||
const customBuild = new Array<string>();
|
||||
|
||||
for (const pkg of packages) {
|
||||
if (!toBuild.has(pkg.packageJson.name)) {
|
||||
continue;
|
||||
}
|
||||
const role = pkg.packageJson.backstage?.role;
|
||||
if (!role) {
|
||||
console.warn(`Ignored ${pkg.packageJson.name} because it has no role`);
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const buildScript = pkg.packageJson.scripts?.build;
|
||||
if (!buildScript) {
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!buildScript.startsWith('backstage-cli script build')) {
|
||||
console.warn(
|
||||
`Ignored ${pkg.packageJson.name} because it has a custom build script, '${buildScript}'`,
|
||||
);
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const outputs = getOutputsForRole(role);
|
||||
|
||||
// No need to build and include types in the production runtime
|
||||
outputs.delete(Output.types);
|
||||
|
||||
if (outputs.size > 0) {
|
||||
standardBuilds.push({
|
||||
targetDir: pkg.dir,
|
||||
outputs: outputs,
|
||||
logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `,
|
||||
// No need to detect these for the backend builds, we assume no minification or types
|
||||
minify: false,
|
||||
useApiExtractor: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await buildPackages(standardBuilds);
|
||||
|
||||
if (customBuild.length > 0) {
|
||||
const scopeArgs = customBuild.flatMap(name => ['--scope', name]);
|
||||
const lernaArgs =
|
||||
options.parallelism && Number.isInteger(options.parallelism)
|
||||
? ['--concurrency', options.parallelism.toString()]
|
||||
: [];
|
||||
|
||||
await run('yarn', ['lerna', ...lernaArgs, 'run', ...scopeArgs, 'build'], {
|
||||
cwd: paths.targetRoot,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await moveToDistWorkspace(targetDir, targets);
|
||||
|
||||
const files: FileEntry[] = options.files ?? ['yarn.lock', 'package.json'];
|
||||
|
||||
for (const file of files) {
|
||||
const src = typeof file === 'string' ? file : file.src;
|
||||
const dest = typeof file === 'string' ? file : file.dest;
|
||||
await fs.copy(paths.resolveTargetRoot(src), resolvePath(targetDir, dest));
|
||||
}
|
||||
|
||||
if (options.skeleton) {
|
||||
const skeletonFiles = targets.map(target => {
|
||||
const dir = relativePath(paths.targetRoot, target.dir);
|
||||
return joinPath(dir, 'package.json');
|
||||
});
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
file: resolvePath(targetDir, options.skeleton),
|
||||
cwd: targetDir,
|
||||
portable: true,
|
||||
noMtime: true,
|
||||
gzip: options.skeleton.endsWith('.gz'),
|
||||
} as CreateOptions & { noMtime: boolean },
|
||||
skeletonFiles,
|
||||
);
|
||||
}
|
||||
|
||||
return targetDir;
|
||||
}
|
||||
|
||||
const FAST_PACK_SCRIPTS = [
|
||||
undefined,
|
||||
'backstage-cli prepack',
|
||||
'backstage-cli script prepack',
|
||||
];
|
||||
|
||||
async function moveToDistWorkspace(
|
||||
workspaceDir: string,
|
||||
localPackages: PackageGraphNode[],
|
||||
): Promise<void> {
|
||||
const [fastPackPackages, slowPackPackages] = partition(localPackages, pkg =>
|
||||
FAST_PACK_SCRIPTS.includes(pkg.packageJson.scripts?.prepack),
|
||||
);
|
||||
|
||||
// New an improved flow where we avoid calling `yarn pack`
|
||||
await Promise.all(
|
||||
fastPackPackages.map(async target => {
|
||||
console.log(`Moving ${target.name} into dist workspace`);
|
||||
|
||||
const outputDir = relativePath(paths.targetRoot, target.dir);
|
||||
const absoluteOutputPath = resolvePath(workspaceDir, outputDir);
|
||||
await copyPackageDist(target.dir, absoluteOutputPath);
|
||||
}),
|
||||
);
|
||||
|
||||
// Old flow is below, which calls `yarn pack` and extracts the tarball
|
||||
|
||||
async function pack(target: PackageGraphNode, archive: string) {
|
||||
console.log(`Repacking ${target.name} into dist workspace`);
|
||||
const archivePath = resolvePath(workspaceDir, archive);
|
||||
|
||||
await run('yarn', ['pack', '--filename', archivePath], {
|
||||
cwd: target.dir,
|
||||
});
|
||||
// TODO(Rugvip): yarn pack doesn't call postpack, once the bug is fixed this can be removed
|
||||
if (target.packageJson?.scripts?.postpack) {
|
||||
await run('yarn', ['postpack'], { cwd: target.dir });
|
||||
}
|
||||
|
||||
const outputDir = relativePath(paths.targetRoot, target.dir);
|
||||
const absoluteOutputPath = resolvePath(workspaceDir, outputDir);
|
||||
await fs.ensureDir(absoluteOutputPath);
|
||||
|
||||
await tar.extract({
|
||||
file: archivePath,
|
||||
cwd: absoluteOutputPath,
|
||||
strip: 1,
|
||||
});
|
||||
await fs.remove(archivePath);
|
||||
|
||||
// We remove the dependencies from package.json of packages that are marked
|
||||
// as bundled, so that yarn doesn't try to install them.
|
||||
if (target.packageJson.bundled) {
|
||||
const pkgJson = await fs.readJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
);
|
||||
delete pkgJson.dependencies;
|
||||
delete pkgJson.devDependencies;
|
||||
delete pkgJson.peerDependencies;
|
||||
delete pkgJson.optionalDependencies;
|
||||
|
||||
await fs.writeJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
pkgJson,
|
||||
{
|
||||
spaces: 2,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [unsafePackages, safePackages] = partition(slowPackPackages, p =>
|
||||
UNSAFE_PACKAGES.includes(p.name),
|
||||
);
|
||||
|
||||
// The unsafe package are packed first one by one in order to avoid race conditions
|
||||
// where the CLI is being executed with broken dependencies.
|
||||
for (const target of unsafePackages) {
|
||||
await pack(target, `temp-package.tgz`);
|
||||
}
|
||||
|
||||
// Repacking in parallel is much faster and safe for all packages outside of the Backstage repo
|
||||
await Promise.all(
|
||||
safePackages.map(async (target, index) =>
|
||||
pack(target, `temp-package-${index}.tgz`),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,213 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import {
|
||||
join as joinPath,
|
||||
resolve as resolvePath,
|
||||
relative as relativePath,
|
||||
} from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import tar, { CreateOptions } from 'tar';
|
||||
import { paths } from '../paths';
|
||||
import { run } from '../run';
|
||||
import { ParallelOption } from '../parallel';
|
||||
import {
|
||||
dependencies as cliDependencies,
|
||||
devDependencies as cliDevDependencies,
|
||||
} from '../../../package.json';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { PackageGraph, PackageGraphNode } from '../monorepo';
|
||||
|
||||
// These packages aren't safe to pack in parallel since the CLI depends on them
|
||||
const UNSAFE_PACKAGES = [
|
||||
...Object.keys(cliDependencies),
|
||||
...Object.keys(cliDevDependencies),
|
||||
];
|
||||
|
||||
type FileEntry =
|
||||
| string
|
||||
| {
|
||||
src: string;
|
||||
dest: string;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
/**
|
||||
* Target directory for the dist workspace, defaults to a temporary directory
|
||||
*/
|
||||
targetDir?: string;
|
||||
|
||||
/**
|
||||
* Files to copy into the target workspace.
|
||||
*
|
||||
* Defaults to ['yarn.lock', 'package.json'].
|
||||
*/
|
||||
files?: FileEntry[];
|
||||
|
||||
/**
|
||||
* If set to true, the target packages are built before they are packaged into the workspace.
|
||||
*/
|
||||
buildDependencies?: boolean;
|
||||
|
||||
/**
|
||||
* When `buildDependencies` is set, this list of packages will not be built even if they are dependencies.
|
||||
*/
|
||||
buildExcludes?: string[];
|
||||
|
||||
/**
|
||||
* Enable (true/false) or control amount of (number) parallelism in some build steps.
|
||||
*/
|
||||
parallel?: ParallelOption;
|
||||
|
||||
/**
|
||||
* If set, creates a skeleton tarball that contains all package.json files
|
||||
* with the same structure as the workspace dir.
|
||||
*/
|
||||
skeleton?: 'skeleton.tar' | 'skeleton.tar.gz';
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses `yarn pack` to package local packages and unpacks them into a dist workspace.
|
||||
* The target workspace will end up containing dist version of each package and
|
||||
* will be suitable for packaging e.g. into a docker image.
|
||||
*
|
||||
* This creates a structure that is functionally similar to if the packages where
|
||||
* installed from npm, but uses Yarn workspaces to link to them at runtime.
|
||||
*/
|
||||
export async function createDistWorkspace(
|
||||
packageNames: string[],
|
||||
options: Options = {},
|
||||
) {
|
||||
const targetDir =
|
||||
options.targetDir ??
|
||||
(await fs.mkdtemp(resolvePath(tmpdir(), 'dist-workspace')));
|
||||
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
const packageGraph = PackageGraph.fromPackages(packages);
|
||||
const targetNames = packageGraph.collectPackageNames(packageNames, node => {
|
||||
// Don't include dependencies of packages that are marked as bundled
|
||||
if (node.packageJson.bundled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return node.publishedLocalDependencies.keys();
|
||||
});
|
||||
const targets = Array.from(targetNames).map(name => packageGraph.get(name)!);
|
||||
|
||||
if (options.buildDependencies) {
|
||||
const exclude = options.buildExcludes ?? [];
|
||||
|
||||
const toBuild = targets.filter(target => !exclude.includes(target.name));
|
||||
if (toBuild.length > 0) {
|
||||
const scopeArgs = toBuild.flatMap(target => ['--scope', target.name]);
|
||||
const lernaArgs =
|
||||
options.parallel && Number.isInteger(options.parallel)
|
||||
? ['--concurrency', options.parallel.toString()]
|
||||
: [];
|
||||
|
||||
await run('yarn', ['lerna', ...lernaArgs, 'run', ...scopeArgs, 'build'], {
|
||||
cwd: paths.targetRoot,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await moveToDistWorkspace(targetDir, targets);
|
||||
|
||||
const files: FileEntry[] = options.files ?? ['yarn.lock', 'package.json'];
|
||||
|
||||
for (const file of files) {
|
||||
const src = typeof file === 'string' ? file : file.src;
|
||||
const dest = typeof file === 'string' ? file : file.dest;
|
||||
await fs.copy(paths.resolveTargetRoot(src), resolvePath(targetDir, dest));
|
||||
}
|
||||
|
||||
if (options.skeleton) {
|
||||
const skeletonFiles = targets.map(target => {
|
||||
const dir = relativePath(paths.targetRoot, target.dir);
|
||||
return joinPath(dir, 'package.json');
|
||||
});
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
file: resolvePath(targetDir, options.skeleton),
|
||||
cwd: targetDir,
|
||||
portable: true,
|
||||
noMtime: true,
|
||||
gzip: options.skeleton.endsWith('.gz'),
|
||||
} as CreateOptions & { noMtime: boolean },
|
||||
skeletonFiles,
|
||||
);
|
||||
}
|
||||
|
||||
return targetDir;
|
||||
}
|
||||
|
||||
async function moveToDistWorkspace(
|
||||
workspaceDir: string,
|
||||
localPackages: PackageGraphNode[],
|
||||
): Promise<void> {
|
||||
async function pack(target: PackageGraphNode, archive: string) {
|
||||
console.log(`Repacking ${target.name} into dist workspace`);
|
||||
const archivePath = resolvePath(workspaceDir, archive);
|
||||
|
||||
await run('yarn', ['pack', '--filename', archivePath], {
|
||||
cwd: target.dir,
|
||||
});
|
||||
// TODO(Rugvip): yarn pack doesn't call postpack, once the bug is fixed this can be removed
|
||||
if (target.packageJson?.scripts?.postpack) {
|
||||
await run('yarn', ['postpack'], { cwd: target.dir });
|
||||
}
|
||||
|
||||
const outputDir = relativePath(paths.targetRoot, target.dir);
|
||||
const absoluteOutputPath = resolvePath(workspaceDir, outputDir);
|
||||
await fs.ensureDir(absoluteOutputPath);
|
||||
|
||||
await tar.extract({
|
||||
file: archivePath,
|
||||
cwd: absoluteOutputPath,
|
||||
strip: 1,
|
||||
});
|
||||
await fs.remove(archivePath);
|
||||
|
||||
// We remove the dependencies from package.json of packages that are marked
|
||||
// as bundled, so that yarn doesn't try to install them.
|
||||
if (target.packageJson.bundled) {
|
||||
const pkgJson = await fs.readJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
);
|
||||
delete pkgJson.dependencies;
|
||||
delete pkgJson.devDependencies;
|
||||
delete pkgJson.peerDependencies;
|
||||
delete pkgJson.optionalDependencies;
|
||||
|
||||
await fs.writeJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
pkgJson,
|
||||
{
|
||||
spaces: 2,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const unsafePackages = localPackages.filter(p =>
|
||||
UNSAFE_PACKAGES.includes(p.name),
|
||||
);
|
||||
const safePackages = localPackages.filter(
|
||||
p => !UNSAFE_PACKAGES.includes(p.name),
|
||||
);
|
||||
|
||||
// The unsafe package are packed first one by one in order to avoid race conditions
|
||||
// where the CLI is being executed with broken dependencies.
|
||||
for (const target of unsafePackages) {
|
||||
await pack(target, `temp-package.tgz`);
|
||||
}
|
||||
|
||||
// Repacking in parallel is much faster and safe for all packages outside of the Backstage repo
|
||||
await Promise.all(
|
||||
safePackages.map(async (target, index) =>
|
||||
pack(target, `temp-package-${index}.tgz`),
|
||||
),
|
||||
);
|
||||
}
|
||||
export { createDistWorkspace } from './createDistWorkspace';
|
||||
|
||||
@@ -14,46 +14,131 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isParallelDefault, parseParallel } from './parallel';
|
||||
import {
|
||||
parseParallelismOption,
|
||||
getEnvironmentParallelism,
|
||||
runParallelWorkers,
|
||||
} from './parallel';
|
||||
|
||||
describe('parallel', () => {
|
||||
describe('parseParallel', () => {
|
||||
it('coerces "false" string to boolean', () => {
|
||||
expect(parseParallel('false')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('coerces "true" to boolean', () => {
|
||||
expect(parseParallel('true')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('coerces number string to number', () => {
|
||||
expect(parseParallel('2')).toBe(2);
|
||||
});
|
||||
it.each([[true], [false], [2]])('returns itself for %p', value => {
|
||||
expect(parseParallel(value as any)).toEqual(value);
|
||||
});
|
||||
|
||||
it.each([[undefined], [null]])('returns true for %p', value => {
|
||||
expect(parseParallel(value as any)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([['on'], [2.5], ['2.5']])('throws error for %p', value => {
|
||||
expect(() => parseParallel(value as any)).toThrowError(
|
||||
`Parallel option value '${value}' is not a boolean or integer`,
|
||||
);
|
||||
});
|
||||
describe('parseParallelismOption', () => {
|
||||
it('coerces false no parallelism', () => {
|
||||
expect(parseParallelismOption(false)).toBe(1);
|
||||
expect(parseParallelismOption('false')).toBe(1);
|
||||
});
|
||||
|
||||
describe('isParallelDefault', () => {
|
||||
it('returns true if default value', () => {
|
||||
expect(isParallelDefault(undefined)).toBeTruthy();
|
||||
expect(isParallelDefault(true)).toBeTruthy();
|
||||
});
|
||||
it('coerces true or undefined to default parallelism', () => {
|
||||
expect(parseParallelismOption(true)).toBe(4);
|
||||
expect(parseParallelismOption('true')).toBe(4);
|
||||
expect(parseParallelismOption(undefined)).toBe(4);
|
||||
expect(parseParallelismOption(null)).toBe(4);
|
||||
});
|
||||
|
||||
it('returns false if not default value', () => {
|
||||
expect(isParallelDefault(false)).toBeFalsy();
|
||||
expect(isParallelDefault(2)).toBeFalsy();
|
||||
expect(isParallelDefault('true' as any)).toBeFalsy();
|
||||
});
|
||||
it('coerces number string to number', () => {
|
||||
expect(parseParallelismOption('2')).toBe(2);
|
||||
});
|
||||
|
||||
it.each([['on'], [2.5], ['2.5']])('throws error for %p', value => {
|
||||
expect(() => parseParallelismOption(value as any)).toThrowError(
|
||||
`Parallel option value '${value}' is not a boolean or integer`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEnvironmentParallelism', () => {
|
||||
it('reads the parallelism setting from the environment', () => {
|
||||
process.env.BACKSTAGE_CLI_BUILD_PARALLEL = '2';
|
||||
expect(getEnvironmentParallelism()).toBe(2);
|
||||
|
||||
process.env.BACKSTAGE_CLI_BUILD_PARALLEL = 'true';
|
||||
expect(getEnvironmentParallelism()).toBe(4);
|
||||
|
||||
process.env.BACKSTAGE_CLI_BUILD_PARALLEL = 'false';
|
||||
expect(getEnvironmentParallelism()).toBe(1);
|
||||
|
||||
delete process.env.BACKSTAGE_CLI_BUILD_PARALLEL;
|
||||
expect(getEnvironmentParallelism()).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runParallelWorkers', () => {
|
||||
it('executes work in parallel', async () => {
|
||||
const started = new Array<number>();
|
||||
const done = new Array<number>();
|
||||
const waiting = new Array<() => void>();
|
||||
|
||||
const work = runParallelWorkers({
|
||||
items: [0, 1, 2, 3, 4],
|
||||
parallelismFactor: 0.5, // 2 at a time
|
||||
worker: async item => {
|
||||
started.push(item);
|
||||
await new Promise<void>(resolve => {
|
||||
waiting[item] = resolve;
|
||||
});
|
||||
done.push(item);
|
||||
},
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1]);
|
||||
expect(done).toEqual([]);
|
||||
waiting[0]();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1, 2]);
|
||||
expect(done).toEqual([0]);
|
||||
waiting[1]();
|
||||
waiting[2]();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1, 2, 3, 4]);
|
||||
expect(done).toEqual([0, 1, 2]);
|
||||
waiting[3]();
|
||||
waiting[4]();
|
||||
|
||||
await work;
|
||||
expect(done).toEqual([0, 1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('executes work sequentially', async () => {
|
||||
const started = new Array<number>();
|
||||
const done = new Array<number>();
|
||||
const waiting = new Array<() => void>();
|
||||
|
||||
const work = runParallelWorkers({
|
||||
items: [0, 1, 2, 3, 4],
|
||||
parallelismFactor: 0, // 1 at a time
|
||||
worker: async item => {
|
||||
started.push(item);
|
||||
await new Promise<void>(resolve => {
|
||||
waiting[item] = resolve;
|
||||
});
|
||||
done.push(item);
|
||||
},
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0]);
|
||||
expect(done).toEqual([]);
|
||||
waiting[0]();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1]);
|
||||
expect(done).toEqual([0]);
|
||||
waiting[1]();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1, 2]);
|
||||
waiting[2]();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1, 2, 3]);
|
||||
waiting[3]();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
expect(started).toEqual([0, 1, 2, 3, 4]);
|
||||
waiting[4]();
|
||||
|
||||
await work;
|
||||
expect(done).toEqual([0, 1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,30 +14,31 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const DEFAULT_PARALLELISM = 4;
|
||||
|
||||
export const PARALLEL_ENV_VAR = 'BACKSTAGE_CLI_BUILD_PARALLEL';
|
||||
|
||||
export type ParallelOption = boolean | number | undefined;
|
||||
export type ParallelismOption = boolean | string | number | null | undefined;
|
||||
|
||||
export function isParallelDefault(parallel: ParallelOption) {
|
||||
return parallel === undefined || parallel === true;
|
||||
}
|
||||
|
||||
export function parseParallel(
|
||||
parallel: boolean | string | number | undefined,
|
||||
): ParallelOption {
|
||||
export function parseParallelismOption(parallel: ParallelismOption): number {
|
||||
if (parallel === undefined || parallel === null) {
|
||||
return true;
|
||||
return DEFAULT_PARALLELISM;
|
||||
} else if (typeof parallel === 'boolean') {
|
||||
return parallel;
|
||||
return parallel ? DEFAULT_PARALLELISM : 1;
|
||||
} else if (typeof parallel === 'number' && Number.isInteger(parallel)) {
|
||||
if (parallel < 1) {
|
||||
return 1;
|
||||
}
|
||||
return parallel;
|
||||
} else if (typeof parallel === 'string') {
|
||||
if (parallel === 'true') {
|
||||
return true;
|
||||
return parseParallelismOption(true);
|
||||
} else if (parallel === 'false') {
|
||||
return false;
|
||||
} else if (Number.isInteger(parseFloat(parallel.toString()))) {
|
||||
return Number(parallel);
|
||||
return parseParallelismOption(false);
|
||||
}
|
||||
const parsed = Number(parallel);
|
||||
if (Number.isInteger(parsed)) {
|
||||
return parseParallelismOption(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,3 +46,45 @@ export function parseParallel(
|
||||
`Parallel option value '${parallel}' is not a boolean or integer`,
|
||||
);
|
||||
}
|
||||
|
||||
export function getEnvironmentParallelism() {
|
||||
return parseParallelismOption(process.env[PARALLEL_ENV_VAR]);
|
||||
}
|
||||
|
||||
type ParallelWorkerOptions<TItem> = {
|
||||
/**
|
||||
* Decides the number of parallel workers by multiplying
|
||||
* this with the configured parallelism, which defaults to 4.
|
||||
*
|
||||
* Defaults to 1.
|
||||
*/
|
||||
parallelismFactor?: number;
|
||||
parallelismSetting?: ParallelismOption;
|
||||
items: Iterable<TItem>;
|
||||
worker: (item: TItem) => Promise<void>;
|
||||
};
|
||||
|
||||
export async function runParallelWorkers<TItem>(
|
||||
options: ParallelWorkerOptions<TItem>,
|
||||
) {
|
||||
const { parallelismFactor = 1, parallelismSetting, items, worker } = options;
|
||||
const parallelism = parallelismSetting
|
||||
? parseParallelismOption(parallelismSetting)
|
||||
: getEnvironmentParallelism();
|
||||
|
||||
const sharedIterator = items[Symbol.iterator]();
|
||||
const sharedIterable = {
|
||||
[Symbol.iterator]: () => sharedIterator,
|
||||
};
|
||||
|
||||
const workerCount = Math.max(Math.floor(parallelismFactor * parallelism), 1);
|
||||
return Promise.all(
|
||||
Array(workerCount)
|
||||
.fill(0)
|
||||
.map(async () => {
|
||||
for (const value of sharedIterable) {
|
||||
await worker(value);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { ErrorPage } from './ErrorPage';
|
||||
import { Link } from '../../components/Link';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('<ErrorPage/>', () => {
|
||||
@@ -30,4 +31,72 @@ describe('<ErrorPage/>', () => {
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('go-back-link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render with additional information of type string', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ErrorPage
|
||||
status="404"
|
||||
statusMessage="PAGE NOT FOUND"
|
||||
additionalInfo="This is a string based additional information"
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
getByText(/looks like someone dropped the mic!/i),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(/This is a string based additional information/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render with additional information including link', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ErrorPage
|
||||
status="404"
|
||||
statusMessage="PAGE NOT FOUND"
|
||||
additionalInfo={
|
||||
<>
|
||||
This is some additional information including{' '}
|
||||
<Link to="/test">a link</Link>
|
||||
</>
|
||||
}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
getByText(/looks like someone dropped the mic!/i),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText(/a link/i)).toBeInTheDocument();
|
||||
expect(getByText(/a link/i)).toHaveAttribute('href', '/test');
|
||||
});
|
||||
|
||||
it('should render with default support url if supportUrl is not provided', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ErrorPage status="404" statusMessage="PAGE NOT FOUND" />,
|
||||
);
|
||||
expect(
|
||||
getByText(/looks like someone dropped the mic!/i),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText(/contact support/i)).toBeInTheDocument();
|
||||
expect(getByText(/contact support/i)).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/backstage/backstage/issues',
|
||||
);
|
||||
});
|
||||
|
||||
it('should override support url if supportUrl property is provided', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ErrorPage
|
||||
status="404"
|
||||
statusMessage="PAGE NOT FOUND"
|
||||
supportUrl="https://error-page-test-support-url.com"
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
getByText(/looks like someone dropped the mic!/i),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText(/contact support/i)).toBeInTheDocument();
|
||||
expect(getByText(/contact support/i)).toHaveAttribute(
|
||||
'href',
|
||||
'https://error-page-test-support-url.com',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,8 @@ import { MicDrop } from './MicDrop';
|
||||
interface IErrorPageProps {
|
||||
status: string;
|
||||
statusMessage: string;
|
||||
additionalInfo?: string;
|
||||
additionalInfo?: React.ReactNode;
|
||||
supportUrl?: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -62,7 +63,7 @@ const useStyles = makeStyles<BackstageTheme>(
|
||||
*
|
||||
*/
|
||||
export function ErrorPage(props: IErrorPageProps) {
|
||||
const { status, statusMessage, additionalInfo } = props;
|
||||
const { status, statusMessage, additionalInfo, supportUrl } = props;
|
||||
const classes = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const support = useSupportConfig();
|
||||
@@ -88,7 +89,8 @@ export function ErrorPage(props: IErrorPageProps) {
|
||||
<Link to="#" data-testid="go-back-link" onClick={() => navigate(-1)}>
|
||||
Go back
|
||||
</Link>
|
||||
... or please <Link to={support.url}>contact support</Link> if you
|
||||
... or please{' '}
|
||||
<Link to={supportUrl || support.url}>contact support</Link> if you
|
||||
think this is a bug.
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"app": "file:../app",
|
||||
"app": "link:../app",
|
||||
"@backstage/backend-common": "^{{version '@backstage/backend-common'}}",
|
||||
"@backstage/backend-tasks": "^{{version '@backstage/backend-tasks'}}",
|
||||
"@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}",
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# NOTE: This file is used for testing techdocs-cli locally
|
||||
|
||||
app:
|
||||
title: Techdocs Preview App
|
||||
baseUrl: http://localhost:3000
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7007
|
||||
|
||||
techdocs:
|
||||
builder: 'external'
|
||||
requestUrl: http://localhost:7007/api
|
||||
@@ -39,9 +39,8 @@
|
||||
"start-server-and-test": "^1.10.11"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli app:serve --config ./app-config.yaml --config ./app-config.dev.yaml",
|
||||
"start": "backstage-cli app:serve --config ./app-config.yaml",
|
||||
"build": "backstage-cli app:build --config ./app-config.yaml",
|
||||
"build:dev": "backstage-cli app:build --config ./app-config.dev.yaml",
|
||||
"clean": "backstage-cli clean",
|
||||
"test": "backstage-cli test",
|
||||
"lint": "backstage-cli lint",
|
||||
|
||||
@@ -18,25 +18,24 @@ import React from 'react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import App from './App';
|
||||
|
||||
jest.mock('./config', () => ({
|
||||
configLoader: async () => [
|
||||
{
|
||||
data: {
|
||||
app: { title: 'Test' },
|
||||
backend: { baseUrl: 'http://localhost:7007' },
|
||||
techdocs: {
|
||||
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
|
||||
},
|
||||
},
|
||||
context: 'test',
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', async () => {
|
||||
process.env = {
|
||||
NODE_ENV: 'test',
|
||||
APP_CONFIG: [
|
||||
{
|
||||
data: {
|
||||
app: { title: 'Test' },
|
||||
backend: { baseUrl: 'http://localhost:7007' },
|
||||
techdocs: {
|
||||
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
|
||||
},
|
||||
},
|
||||
context: 'test',
|
||||
},
|
||||
] as any,
|
||||
};
|
||||
|
||||
const rendered = await renderWithEffects(<App />);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
expect(rendered.getByText('Docs Preview')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,9 +29,11 @@ import { apis } from './apis';
|
||||
import { Root } from './components/Root';
|
||||
import { techDocsPage } from './components/TechDocsPage';
|
||||
import * as plugins from './plugins';
|
||||
import { configLoader } from './config';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
configLoader,
|
||||
plugins: Object.values(plugins),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 { defaultConfigLoader } from '@backstage/core-app-api';
|
||||
|
||||
const PRODUCTION_CONFIG = {
|
||||
backend: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
},
|
||||
techdocs: {
|
||||
builder: 'external',
|
||||
requestUrl: 'http://localhost:3000/api',
|
||||
},
|
||||
};
|
||||
|
||||
const DEVELOPMENT_CONFIG = {
|
||||
backend: {
|
||||
baseUrl: 'http://localhost:7007',
|
||||
},
|
||||
techdocs: {
|
||||
builder: 'external',
|
||||
requestUrl: 'http://localhost:7007/api',
|
||||
},
|
||||
};
|
||||
|
||||
async function isProductionServe() {
|
||||
const res = await fetch('/.detect');
|
||||
if (!res.ok) {
|
||||
return false;
|
||||
}
|
||||
const text = await res.text();
|
||||
return text.trim() === 'techdocs-cli-server';
|
||||
}
|
||||
|
||||
export async function configLoader() {
|
||||
const defaultConfigs = await defaultConfigLoader();
|
||||
const isProduction = await isProductionServe();
|
||||
|
||||
return [
|
||||
...defaultConfigs,
|
||||
{
|
||||
context: 'detected',
|
||||
data: isProduction ? PRODUCTION_CONFIG : DEVELOPMENT_CONFIG,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -21,12 +21,13 @@
|
||||
"types": "",
|
||||
"scripts": {
|
||||
"start": "nodemon --",
|
||||
"build": "./scripts/build.sh",
|
||||
"build": "backstage-cli build --outputs cjs",
|
||||
"clean": "backstage-cli clean",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test --testPathIgnorePatterns=src/e2e.test.ts",
|
||||
"test:e2e": "backstage-cli test src/e2e.test.ts",
|
||||
"test:e2e:ci": "backstage-cli test --watchAll=false --ci src/e2e.test.ts"
|
||||
"test:e2e:ci": "backstage-cli test --watchAll=false --ci src/e2e.test.ts",
|
||||
"prepack": "./scripts/prepack.sh"
|
||||
},
|
||||
"bin": {
|
||||
"techdocs-cli": "bin/techdocs-cli"
|
||||
@@ -40,7 +41,7 @@
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/serve-handler": "^6.1.0",
|
||||
"@types/webpack-env": "^1.15.3",
|
||||
"techdocs-cli-embedded-app": "file:../techdocs-cli-embedded-app",
|
||||
"techdocs-cli-embedded-app": "link:../techdocs-cli-embedded-app",
|
||||
"find-process": "^1.4.5",
|
||||
"nodemon": "^2.0.2",
|
||||
"ts-node": "^10.0.0"
|
||||
@@ -57,6 +58,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.10.6",
|
||||
"@backstage/catalog-model": "^0.9.10",
|
||||
"@backstage/cli-common": "^0.1.6",
|
||||
"@backstage/config": "^0.1.13",
|
||||
"@backstage/techdocs-common": "^0.11.6",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
|
||||
@@ -20,25 +20,7 @@ SCRIPT_DIR=$(dirname $0)
|
||||
TECHDOCS_CLI_DIR="$SCRIPT_DIR"/..
|
||||
TECHDOCS_CLI_EMBEDDED_APP_DIR="$TECHDOCS_CLI_DIR"/../techdocs-cli-embedded-app
|
||||
|
||||
compile_and_build_cli() {
|
||||
echo "📄 Compiling..."
|
||||
yarn workspace @techdocs/cli tsc > /dev/null
|
||||
echo "📦️ Building..."
|
||||
pushd $TECHDOCS_CLI_DIR > /dev/null
|
||||
npx backstage-cli build --outputs cjs > /dev/null
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
build_and_embed_app() {
|
||||
echo "🚚 Embedding app..."
|
||||
if [ "$TECHDOCS_CLI_DEV_MODE" = "true" ] ; then
|
||||
yarn workspace techdocs-cli-embedded-app build:dev > /dev/null
|
||||
else
|
||||
yarn workspace techdocs-cli-embedded-app build > /dev/null
|
||||
fi
|
||||
cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/techdocs-preview-bundle > /dev/null
|
||||
}
|
||||
|
||||
compile_and_build_cli
|
||||
build_and_embed_app
|
||||
echo "🚚 Copying embedded app into dist/embedded-app"
|
||||
rm -rf "$TECHDOCS_CLI_DIR"/dist/embedded-app
|
||||
cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/embedded-app
|
||||
echo "🏁 Ready!"
|
||||
@@ -17,11 +17,30 @@
|
||||
import { Command } from 'commander';
|
||||
import path from 'path';
|
||||
import openBrowser from 'react-dev-utils/openBrowser';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import HTTPServer from '../../lib/httpServer';
|
||||
import { runMkdocsServer } from '../../lib/mkdocsServer';
|
||||
import { LogFunc, waitForSignal } from '../../lib/run';
|
||||
import { createLogger } from '../../lib/utility';
|
||||
|
||||
function findPreviewBundlePath(): string {
|
||||
try {
|
||||
return path.join(
|
||||
path.dirname(require.resolve('techdocs-cli-embedded-app/package.json')),
|
||||
'dist',
|
||||
);
|
||||
} catch {
|
||||
// If the techdocs-cli-embedded-app package is not available it means we're
|
||||
// running a published package. For published packages the preview bundle is
|
||||
// copied to dist/embedded-app be the prepack script.
|
||||
//
|
||||
// This can be tested by running `yarn pack` and extracting the resulting tarball into a directory.
|
||||
// Within the extracted directory, run `npm install --only=prod`.
|
||||
// Once that's done you can test the CLI in any directory using `node <tmp-dir>/package <command>`.
|
||||
return findPaths(__dirname).resolveOwn('dist/embedded-app');
|
||||
}
|
||||
}
|
||||
|
||||
export default async function serve(cmd: Command) {
|
||||
const logger = createLogger({ verbose: cmd.verbose });
|
||||
|
||||
@@ -91,16 +110,9 @@ export default async function serve(cmd: Command) {
|
||||
);
|
||||
}
|
||||
|
||||
// Run the embedded-techdocs Backstage app
|
||||
const techdocsPreviewBundlePath = path.join(
|
||||
path.dirname(require.resolve('@techdocs/cli/package.json')),
|
||||
'dist',
|
||||
'techdocs-preview-bundle',
|
||||
);
|
||||
|
||||
const port = isDevMode ? backstageBackendPort : backstagePort;
|
||||
const httpServer = new HTTPServer(
|
||||
techdocsPreviewBundlePath,
|
||||
findPreviewBundlePath(),
|
||||
port,
|
||||
cmd.mkdocsPort,
|
||||
cmd.verbose,
|
||||
|
||||
@@ -71,10 +71,18 @@ export default class HTTPServer {
|
||||
response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
|
||||
request.url = forwardPath;
|
||||
return proxy.web(request, response);
|
||||
proxy.web(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
return serveHandler(request, response, {
|
||||
// This endpoint is used by the frontend to detect where the backend is running.
|
||||
if (request.url === '/.detect') {
|
||||
response.setHeader('Content-Type', 'text/plain');
|
||||
response.end('techdocs-cli-server');
|
||||
return;
|
||||
}
|
||||
|
||||
serveHandler(request, response, {
|
||||
public: this.backstageBundleDir,
|
||||
trailingSlash: true,
|
||||
rewrites: [{ source: '**', destination: 'index.html' }],
|
||||
|
||||
@@ -214,6 +214,17 @@ export class CatalogIdentityClient {
|
||||
resolveCatalogMembership(query: MemberClaimQuery): Promise<string[]>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type CookieConfigurer = (ctx: {
|
||||
providerId: string;
|
||||
baseUrl: string;
|
||||
callbackUrl: string;
|
||||
}) => {
|
||||
domain: string;
|
||||
path: string;
|
||||
secure: boolean;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createAtlassianProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -736,5 +747,5 @@ export type WebMessageResponse =
|
||||
// src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/aws-alb/provider.d.ts:77:5 - (ae-forgotten-export) The symbol "AwsAlbResult" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/github/provider.d.ts:97:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:98:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:118:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
@@ -69,13 +69,14 @@ describe('OAuthAdapter', () => {
|
||||
secure: false,
|
||||
disableRefresh: true,
|
||||
appOrigin: 'http://localhost:3000',
|
||||
cookieDomain: 'localhost',
|
||||
cookieDomain: 'example.com',
|
||||
cookiePath: '/auth/test-provider',
|
||||
tokenIssuer: {
|
||||
issueToken: async () => 'my-id-token',
|
||||
listPublicKeys: async () => ({ keys: [] }),
|
||||
},
|
||||
isOriginAllowed: () => false,
|
||||
callbackUrl: 'http://example.com:7007/auth/test-provider/frame/handler',
|
||||
};
|
||||
|
||||
it('sets the correct headers in start', async () => {
|
||||
@@ -444,47 +445,6 @@ describe('OAuthAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the correct cookie configuration using the base url', async () => {
|
||||
const config = {
|
||||
baseUrl: 'http://domain.org/auth',
|
||||
appUrl: 'http://domain.org',
|
||||
isOriginAllowed: () => false,
|
||||
};
|
||||
|
||||
const oauthProvider = OAuthAdapter.fromConfig(
|
||||
config,
|
||||
providerInstance,
|
||||
oAuthProviderOptions,
|
||||
);
|
||||
|
||||
const mockRequest = {
|
||||
query: {
|
||||
scope: 'user',
|
||||
env: 'development',
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
|
||||
const mockResponse = {
|
||||
cookie: jest.fn().mockReturnThis(),
|
||||
end: jest.fn().mockReturnThis(),
|
||||
setHeader: jest.fn().mockReturnThis(),
|
||||
statusCode: jest.fn().mockReturnThis(),
|
||||
} as unknown as express.Response;
|
||||
|
||||
await oauthProvider.start(mockRequest, mockResponse);
|
||||
|
||||
expect(mockResponse.cookie).toBeCalledTimes(1);
|
||||
expect(mockResponse.cookie).toBeCalledWith(
|
||||
`${oAuthProviderOptions.providerId}-nonce`,
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
domain: 'domain.org',
|
||||
path: '/auth/test-provider/handler',
|
||||
secure: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('sets the correct cookie configuration using a callbackUrl', async () => {
|
||||
const config = {
|
||||
baseUrl: 'http://domain.org/auth',
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
NotAllowedError,
|
||||
} from '@backstage/errors';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { getCookieConfig, readState, verifyNonce } from './helpers';
|
||||
import { defaultCookieConfigurer, readState, verifyNonce } from './helpers';
|
||||
import { postMessageResponse, ensuresXRequestedWith } from '../flow';
|
||||
import {
|
||||
OAuthHandlers,
|
||||
@@ -58,7 +58,7 @@ export type Options = {
|
||||
appOrigin: string;
|
||||
tokenIssuer: TokenIssuer;
|
||||
isOriginAllowed: (origin: string) => boolean;
|
||||
callbackUrl?: string;
|
||||
callbackUrl: string;
|
||||
};
|
||||
export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
static fromConfig(
|
||||
@@ -74,18 +74,20 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
>,
|
||||
): OAuthAdapter {
|
||||
const { origin: appOrigin } = new URL(config.appUrl);
|
||||
const authUrl = new URL(options.callbackUrl ?? config.baseUrl);
|
||||
const { cookieDomain, cookiePath, secure } = getCookieConfig(
|
||||
authUrl,
|
||||
options.providerId,
|
||||
);
|
||||
|
||||
const cookieConfigurer = config.cookieConfigurer ?? defaultCookieConfigurer;
|
||||
const cookieConfig = cookieConfigurer({
|
||||
providerId: options.providerId,
|
||||
baseUrl: config.baseUrl,
|
||||
callbackUrl: options.callbackUrl,
|
||||
});
|
||||
|
||||
return new OAuthAdapter(handlers, {
|
||||
...options,
|
||||
appOrigin,
|
||||
cookieDomain,
|
||||
cookiePath,
|
||||
secure,
|
||||
cookieDomain: cookieConfig.domain,
|
||||
cookiePath: cookieConfig.path,
|
||||
secure: cookieConfig.secure,
|
||||
isOriginAllowed: config.isOriginAllowed,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
verifyNonce,
|
||||
encodeState,
|
||||
readState,
|
||||
getCookieConfig,
|
||||
defaultCookieConfigurer,
|
||||
} from './helpers';
|
||||
|
||||
describe('OAuthProvider Utils', () => {
|
||||
@@ -110,30 +110,43 @@ describe('OAuthProvider Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCookieConfig', () => {
|
||||
describe('defaultCookieConfigurer', () => {
|
||||
it('should set the correct domain and path for a base url', () => {
|
||||
const mockAuthUrl = new URL('http://domain.org/auth');
|
||||
expect(getCookieConfig(mockAuthUrl, 'test-provider')).toMatchObject({
|
||||
cookieDomain: 'domain.org',
|
||||
cookiePath: '/auth/test-provider',
|
||||
expect(
|
||||
defaultCookieConfigurer({
|
||||
baseUrl: '',
|
||||
providerId: 'test-provider',
|
||||
callbackUrl: 'http://domain.org/auth',
|
||||
}),
|
||||
).toMatchObject({
|
||||
domain: 'domain.org',
|
||||
path: '/auth/test-provider',
|
||||
secure: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the correct domain and path for a url containing a frame handler', () => {
|
||||
const mockAuthUrl = new URL(
|
||||
'http://domain.org/auth/test-provider/handler/frame',
|
||||
);
|
||||
expect(getCookieConfig(mockAuthUrl, 'test-provider')).toMatchObject({
|
||||
cookieDomain: 'domain.org',
|
||||
cookiePath: '/auth/test-provider',
|
||||
expect(
|
||||
defaultCookieConfigurer({
|
||||
baseUrl: '',
|
||||
providerId: 'test-provider',
|
||||
callbackUrl: 'http://domain.org/auth/test-provider/handler/frame',
|
||||
}),
|
||||
).toMatchObject({
|
||||
domain: 'domain.org',
|
||||
path: '/auth/test-provider',
|
||||
secure: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the secure flag if url is using https', () => {
|
||||
const mockAuthUrl = new URL('https://domain.org/auth');
|
||||
expect(getCookieConfig(mockAuthUrl, 'test-provider')).toMatchObject({
|
||||
expect(
|
||||
defaultCookieConfigurer({
|
||||
baseUrl: '',
|
||||
providerId: 'test-provider',
|
||||
callbackUrl: 'https://domain.org/auth',
|
||||
}),
|
||||
).toMatchObject({
|
||||
secure: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import express from 'express';
|
||||
import { OAuthState } from './types';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import { CookieConfigurer } from '../../providers/types';
|
||||
|
||||
export const readState = (stateString: string): OAuthState => {
|
||||
const state = Object.fromEntries(
|
||||
@@ -58,20 +59,19 @@ export const verifyNonce = (req: express.Request, providerId: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getCookieConfig = (authUrl: URL, providerId: string) => {
|
||||
const { hostname: cookieDomain, pathname, protocol } = authUrl;
|
||||
export const defaultCookieConfigurer: CookieConfigurer = ({
|
||||
callbackUrl,
|
||||
providerId,
|
||||
}) => {
|
||||
const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
|
||||
const secure = protocol === 'https:';
|
||||
|
||||
// If the provider supports callbackUrls, the pathname will
|
||||
// contain the complete path to the frame handler so we need
|
||||
// to slice off the trailing part of the path.
|
||||
const cookiePath = pathname.endsWith(`${providerId}/handler/frame`)
|
||||
const path = pathname.endsWith(`${providerId}/handler/frame`)
|
||||
? pathname.slice(0, -'/handler/frame'.length)
|
||||
: `${pathname}/${providerId}`;
|
||||
|
||||
return {
|
||||
cookieDomain,
|
||||
cookiePath,
|
||||
secure,
|
||||
};
|
||||
return { domain, path, secure };
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ export type {
|
||||
AuthHandlerResult,
|
||||
SignInResolver,
|
||||
SignInInfo,
|
||||
CookieConfigurer,
|
||||
} from './types';
|
||||
|
||||
// These types are needed for a postMessage from the login pop-up
|
||||
|
||||
@@ -38,6 +38,19 @@ export type AuthResolverContext = {
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
/**
|
||||
* The callback used to resolve the cookie configuration for auth providers that use cookies.
|
||||
* @public
|
||||
*/
|
||||
export type CookieConfigurer = (ctx: {
|
||||
/** ID of the auth provider that this configuration applies to */
|
||||
providerId: string;
|
||||
/** The externally reachable base URL of the auth-backend plugin */
|
||||
baseUrl: string;
|
||||
/** The configured callback URL of the auth provider */
|
||||
callbackUrl: string;
|
||||
}) => { domain: string; path: string; secure: boolean };
|
||||
|
||||
export type AuthProviderConfig = {
|
||||
/**
|
||||
* The protocol://domain[:port] where the app is hosted. This is used to construct the
|
||||
@@ -54,6 +67,11 @@ export type AuthProviderConfig = {
|
||||
* A function that is called to check whether an origin is allowed to receive the authentication result.
|
||||
*/
|
||||
isOriginAllowed: (origin: string) => boolean;
|
||||
|
||||
/**
|
||||
* The function used to resolve cookie configuration based on the auth provider options.
|
||||
*/
|
||||
cookieConfigurer?: CookieConfigurer;
|
||||
};
|
||||
|
||||
export type RedirectInfo = {
|
||||
|
||||
@@ -111,7 +111,11 @@ export async function createRouter(
|
||||
try {
|
||||
const provider = providerFactory({
|
||||
providerId,
|
||||
globalConfig: { baseUrl: authUrl, appUrl, isOriginAllowed },
|
||||
globalConfig: {
|
||||
baseUrl: authUrl,
|
||||
appUrl,
|
||||
isOriginAllowed,
|
||||
},
|
||||
config: providersConfig.getConfig(providerId),
|
||||
logger,
|
||||
tokenManager,
|
||||
|
||||
@@ -149,6 +149,7 @@ export type MicrosoftGraphProviderConfig = {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
userFilter?: string;
|
||||
userExpand?: string[];
|
||||
userGroupMemberFilter?: string;
|
||||
groupFilter?: string;
|
||||
};
|
||||
@@ -178,6 +179,7 @@ export function readMicrosoftGraphOrg(
|
||||
client: MicrosoftGraphClient,
|
||||
tenantId: string,
|
||||
options: {
|
||||
userExpand?: string[];
|
||||
userFilter?: string;
|
||||
userGroupMemberFilter?: string;
|
||||
groupFilter?: string;
|
||||
|
||||
@@ -52,6 +52,12 @@ export type MicrosoftGraphProviderConfig = {
|
||||
* E.g. "accountEnabled eq true and userType eq 'member'"
|
||||
*/
|
||||
userFilter?: string;
|
||||
/**
|
||||
* The expand argument to apply to users.
|
||||
*
|
||||
* E.g. "manager"
|
||||
*/
|
||||
userExpand?: string[];
|
||||
/**
|
||||
* The filter to apply to extract users by groups memberships.
|
||||
*
|
||||
|
||||
@@ -85,6 +85,7 @@ export async function readMicrosoftGraphUsers(
|
||||
client: MicrosoftGraphClient,
|
||||
options: {
|
||||
userFilter?: string;
|
||||
userExpand?: string[];
|
||||
transformer?: UserTransformer;
|
||||
logger: Logger;
|
||||
},
|
||||
@@ -99,6 +100,7 @@ export async function readMicrosoftGraphUsers(
|
||||
|
||||
for await (const user of client.getUsers({
|
||||
filter: options.userFilter,
|
||||
expand: options.userExpand,
|
||||
})) {
|
||||
// Process all users in parallel, otherwise it can take quite some time
|
||||
promises.push(
|
||||
@@ -500,6 +502,7 @@ export async function readMicrosoftGraphOrg(
|
||||
client: MicrosoftGraphClient,
|
||||
tenantId: string,
|
||||
options: {
|
||||
userExpand?: string[];
|
||||
userFilter?: string;
|
||||
userGroupMemberFilter?: string;
|
||||
groupFilter?: string;
|
||||
@@ -524,6 +527,7 @@ export async function readMicrosoftGraphOrg(
|
||||
} else {
|
||||
const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, {
|
||||
userFilter: options.userFilter,
|
||||
userExpand: options.userExpand,
|
||||
transformer: options.userTransformer,
|
||||
logger: options.logger,
|
||||
});
|
||||
|
||||
+1
@@ -102,6 +102,7 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor {
|
||||
client,
|
||||
provider.tenantId,
|
||||
{
|
||||
userExpand: provider.userExpand,
|
||||
userFilter: provider.userFilter,
|
||||
userGroupMemberFilter: provider.userGroupMemberFilter,
|
||||
groupFilter: provider.groupFilter,
|
||||
|
||||
@@ -9,6 +9,19 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
export const DashboardSnapshotComponent: ({
|
||||
guid,
|
||||
name,
|
||||
permalink,
|
||||
duration,
|
||||
}: {
|
||||
guid: string;
|
||||
name: string;
|
||||
permalink: string;
|
||||
duration: number;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityNewRelicDashboardCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -17,5 +17,6 @@ export {
|
||||
newRelicDashboardPlugin,
|
||||
EntityNewRelicDashboardCard,
|
||||
EntityNewRelicDashboardContent,
|
||||
DashboardSnapshotComponent,
|
||||
} from './plugin';
|
||||
export { isNewRelicDashboardAvailable } from './Router';
|
||||
|
||||
@@ -60,3 +60,22 @@ export const EntityNewRelicDashboardCard = newRelicDashboardPlugin.provide(
|
||||
},
|
||||
}),
|
||||
);
|
||||
/**
|
||||
* Render dashboard snapshots from Newrelic in backstage. Use dashboards which have the tag `isDashboardPage: true`
|
||||
*
|
||||
* @remarks
|
||||
* This can be helpful for rendering dashboards outside of Entity Catalog.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const DashboardSnapshotComponent = newRelicDashboardPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'DashboardSnapshotComponent',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import(
|
||||
'./components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot'
|
||||
).then(m => m.DashboardSnapshot),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Lifecycle,
|
||||
Page,
|
||||
LogViewer,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
@@ -352,6 +353,8 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
{!currentStepId && <Progress />}
|
||||
|
||||
<div style={{ height: '80vh' }}>
|
||||
<LogViewer text={logAsString} />
|
||||
</div>
|
||||
|
||||
@@ -25,31 +25,17 @@ const baseOptions = {
|
||||
repo: 'backstage',
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const { GITHUB_SHA, GITHUB_TOKEN } = process.env;
|
||||
if (!GITHUB_SHA) {
|
||||
throw new Error('GITHUB_SHA is not set');
|
||||
}
|
||||
if (!GITHUB_TOKEN) {
|
||||
throw new Error('GITHUB_TOKEN is not set');
|
||||
}
|
||||
|
||||
const octokit = new Octokit({ auth: GITHUB_TOKEN });
|
||||
|
||||
const rootPath = path.resolve(__dirname, '..');
|
||||
const { version: currentVersion } = await fs.readJson(
|
||||
path.join(rootPath, 'package.json'),
|
||||
);
|
||||
|
||||
const tagName = `v${currentVersion}`;
|
||||
|
||||
console.log(`Creating release tag ${tagName}`);
|
||||
async function getCurrentReleaseTag() {
|
||||
const rootPath = path.resolve(__dirname, '../package.json');
|
||||
return fs.readJson(rootPath).then(_ => _.version);
|
||||
}
|
||||
|
||||
async function createGitTag(octokit, commitSha, tagName) {
|
||||
const annotatedTag = await octokit.git.createTag({
|
||||
...baseOptions,
|
||||
tag: tagName,
|
||||
message: tagName,
|
||||
object: GITHUB_SHA,
|
||||
object: commitSha,
|
||||
type: 'commit',
|
||||
});
|
||||
|
||||
@@ -69,8 +55,46 @@ async function main() {
|
||||
console.error(`Tag creation for ${tagName} failed`);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
async function dispatchReleaseWorkflows(octokit, releaseVersion) {
|
||||
console.log('Dispatching release manifest sync');
|
||||
await octokit.actions.createWorkflowDispatch({
|
||||
owner: 'backstage',
|
||||
repo: 'backstage',
|
||||
workflow_id: 'sync_release-manifest.yml',
|
||||
ref: 'master',
|
||||
inputs: {
|
||||
version: releaseVersion,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const shouldDispatch = process.argv.includes('--dispatch-workflows');
|
||||
|
||||
if (!process.env.GITHUB_SHA) {
|
||||
throw new Error('GITHUB_SHA is not set');
|
||||
}
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
throw new Error('GITHUB_TOKEN is not set');
|
||||
}
|
||||
|
||||
const commitSha = process.env.GITHUB_SHA;
|
||||
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
||||
|
||||
const releaseVersion = await getCurrentReleaseTag();
|
||||
const tagName = `v${releaseVersion}`;
|
||||
|
||||
console.log(`Creating release tag ${tagName} at ${commitSha}`);
|
||||
await createGitTag(octokit, commitSha, tagName);
|
||||
|
||||
console.log(`::set-output name=tag_name::${tagName}`);
|
||||
|
||||
if (shouldDispatch) {
|
||||
console.log(`Dispatching release workflows for ${tagName}`);
|
||||
await dispatchReleaseWorkflows(octokit, releaseVersion);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
"storybook-dark-mode": "^1.0.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/theme": "file:../packages/theme",
|
||||
"@backstage/test-utils": "file:../packages/test-utils",
|
||||
"@backstage/core-app-api": "file:../packages/core-app-api",
|
||||
"@backstage/core-plugin-api": "file:../packages/core-plugin-api"
|
||||
"@backstage/theme": "link:../packages/theme",
|
||||
"@backstage/test-utils": "link:../packages/test-utils",
|
||||
"@backstage/core-app-api": "link:../packages/core-app-api",
|
||||
"@backstage/core-plugin-api": "link:../packages/core-plugin-api"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.0.0"
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3":
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
|
||||
integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
|
||||
@@ -5761,6 +5761,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
|
||||
|
||||
"@types/npm-packlist@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/@types/npm-packlist/-/npm-packlist-1.1.2.tgz#285978c9023ce68fa0641ca606c7c3b7b0e851c5"
|
||||
integrity sha512-9NYoEH87t90e6dkaQOuUTY/R1xUE0a67sXzJBuAB+b+/z4FysHFD19g/O154ToGjyWqKYkezVUtuBdtfd4hyfw==
|
||||
|
||||
"@types/nunjucks@^3.1.4":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.2.1.tgz#02a3ade3dc4d3950029c6466a4034565dba7cf8c"
|
||||
@@ -7300,10 +7305,10 @@ async@^2.6.2:
|
||||
dependencies:
|
||||
lodash "^4.17.14"
|
||||
|
||||
async@^3.1.0, async@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
||||
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
|
||||
async@^3.2.0, async@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.npmjs.org/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
@@ -8404,10 +8409,10 @@ check-types@^11.1.1:
|
||||
resolved "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
|
||||
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
|
||||
|
||||
chokidar@^3.2.2, chokidar@^3.3.1, chokidar@^3.4.2, chokidar@^3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
||||
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
||||
chokidar@^3.2.2, chokidar@^3.3.1, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
@@ -8792,7 +8797,7 @@ colors@1.0.3:
|
||||
resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
|
||||
|
||||
colors@1.4.0, colors@^1.1.2, colors@^1.2.1:
|
||||
colors@1.4.0, colors@^1.1.2:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
|
||||
@@ -10885,65 +10890,65 @@ es6-weak-map@^2.0.3:
|
||||
es6-iterator "^2.0.3"
|
||||
es6-symbol "^3.1.1"
|
||||
|
||||
esbuild-android-arm64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.10.tgz#c854db57dc2d4df6f4f62185ca812f26a132bf1e"
|
||||
integrity sha512-vzkTafHKoiMX4uIN1kBnE/HXYLpNT95EgGanVk6DHGeYgDolU0NBxjO7yZpq4ZGFPOx8384eAdDrBYhO11TAlQ==
|
||||
esbuild-android-arm64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.20.tgz#7d1e7391030d83e2d6745ac297d630bb33130b36"
|
||||
integrity sha512-MPKVDe3TMjGDRB5WmY9XnBaXEsPiiTpkz6GjXgBhBkMFZm27PhvZT4JE0vZ1fsLb5hnGC/fYsfAnp9rsxTZhIg==
|
||||
|
||||
esbuild-darwin-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.10.tgz#c44fab6b8bfc83e5d083f513e4acbff14fb82eac"
|
||||
integrity sha512-DJwzFVB95ZV7C3PQbf052WqaUuuMFXJeZJ0LKdnP1w+QOU0rlbKfX0tzuhoS//rOXUj1TFIwRuRsd0FX6skR7A==
|
||||
esbuild-darwin-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.20.tgz#b2633db8e87e36197965f18b6c0cfabc3497d8d2"
|
||||
integrity sha512-09PPWejM3rRFsGHvtaTuRlG+KOQlOMwPW4HwwzRlO4TuP+FNV1nTW4x2Nid3dYLzCkcjznJWQ0oylLBQvGTRyQ==
|
||||
|
||||
esbuild-darwin-arm64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.10.tgz#9454b3763b36407dc395c4c3529fb5ddd4a6225f"
|
||||
integrity sha512-RNaaoZDg3nsqs5z56vYCjk/VJ76npf752W0rOaCl5lO5TsgV9zecfdYgt7dtUrIx8b7APhVaNYud+tGsDOVC9g==
|
||||
esbuild-darwin-arm64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.20.tgz#180fbebda4ec9376ffd8247a3d488f95c1d9df69"
|
||||
integrity sha512-jYLrSXAwygoFF2lpRJSUAghre+9IThbcPvJQbcZMONBQaaZft9nclNsrN3k4u7zQaC8v+xZDVSHkmw593tQvkg==
|
||||
|
||||
esbuild-freebsd-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.10.tgz#04eef46d5d5e4152c6b5a6a12f432db0fe7c89de"
|
||||
integrity sha512-10B3AzW894u6bGZZhWiJOHw1uEHb4AFbUuBdyml1Ht0vIqd+KqWW+iY/yMwQAzILr2WJZqEhbOXRkJtY8aRqOw==
|
||||
esbuild-freebsd-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.20.tgz#4eb99ccf3e0b7ab039e5bbe491a44458991006c2"
|
||||
integrity sha512-XShznPLW3QsK8/7iCx1euZTowWaWlcrlkq4YTlRqDKXkJRe98FJ6+V2QyoSTwwCoo5koaYwc+h/SYdglF5369A==
|
||||
|
||||
esbuild-freebsd-arm64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.10.tgz#67ca88529543ada948737c95819253ead16494a7"
|
||||
integrity sha512-mSQrKB7UaWvuryBTCo9leOfY2uEUSimAvcKIaUWbk5Hth9Sg+Try+qNA/NibPgs/vHkX0KFo/Rce6RPea+P15g==
|
||||
esbuild-freebsd-arm64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.20.tgz#5c6a02a4bc8ec8ff96c1142cf1509f1494aa78ff"
|
||||
integrity sha512-flb3tDd6SScKhBqzWAESVCErpaqrGmMSRrssjx1aC+Ai5ZQrEyhfs5OWL4A9qHuixkhfmXffci7rFD+bNeXmZg==
|
||||
|
||||
esbuild-linux-32@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.10.tgz#8f3d5fb0b9b616d6b604da781d71767d7679f64f"
|
||||
integrity sha512-lktF09JgJLZ63ANYHIPdYe339PDuVn19Q/FcGKkXWf+jSPkn5xkYzAabboNGZNUgNqSJ/vY7VrOn6UrBbJjgYA==
|
||||
esbuild-linux-32@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.20.tgz#76af613e60a4f366d54d5d186c678bac36b18eda"
|
||||
integrity sha512-Avtxbd0MHFJ2QhNxj/e8VGGm1/VnEJZq9qiHUl3wQZ4S0o2Wf4ReAfhqmgAbOPFTuxuZm070rRDZYiZifWzFGQ==
|
||||
|
||||
esbuild-linux-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.10.tgz#c1c60a079c4709164bdd89fbb007a2edeea7c34a"
|
||||
integrity sha512-K+gCQz2oLIIBI8ZM77e9sYD5/DwEpeYCrOQ2SYXx+R4OU2CT9QjJDi4/OpE7ko4AcYMlMW7qrOCuLSgAlEj4Wg==
|
||||
esbuild-linux-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.20.tgz#35d3c7d792403b913c308c92942c3f6893dc8225"
|
||||
integrity sha512-ugisoRA/ajCr9JMszsQnT9hKkpbD7Gr1yl1mWdZhWQnGt6JKGIndGiihMURcrR44IK/2OMkixVe66D4gCHKdPA==
|
||||
|
||||
esbuild-linux-arm64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.10.tgz#d8f1f89190f6d8b6e06a1214aafba454e5daa990"
|
||||
integrity sha512-+qocQuQvcp5wo/V+OLXxqHPc+gxHttJEvbU/xrCGE03vIMqraL4wMua8JQx0SWEnJCWP+Nhf//v8OSwz1Xr5kA==
|
||||
esbuild-linux-arm64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.20.tgz#489e9187f95ce15e07e15a2aaadc53ec5ce1a02c"
|
||||
integrity sha512-hsrMbNzhh+ud3zUyhONlR41vpYMjINS7BHEzXHbzo4YiCsG9Ht3arbiSuNGrhR/ybLr+8J/0fYVCipiVeAjy3Q==
|
||||
|
||||
esbuild-linux-arm@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.10.tgz#43192a00019a4553fb44e67f628fff0f560f16c2"
|
||||
integrity sha512-BYa60dZ/KPmNKYxtHa3LSEdfKWHcm/RzP0MjB4AeBPhjS0D6/okhaBesZIY9kVIGDyeenKsJNOmnVt4+dhNnvQ==
|
||||
esbuild-linux-arm@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.20.tgz#40c0f5aab33b8fe04e0528a6b8a073e9fb2ba6fd"
|
||||
integrity sha512-uo++Mo31+P2EA38oQgOeSIWgD7GMCMpZkaLfsCqtKJTIIL9fVzQHQYLDRIiFGpLHvs1faWWHDCEcXEFSP1Ou0g==
|
||||
|
||||
esbuild-linux-mips64le@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.10.tgz#f57bb8b2f1a3063cc91cfd787c8a9130cf863c16"
|
||||
integrity sha512-nmUd2xoBXpGo4NJCEWoaBj+n4EtDoLEvEYc8Z3aSJrY0Oa6s04czD1flmhd0I/d6QEU8b7GQ9U0g/rtBfhtxBg==
|
||||
esbuild-linux-mips64le@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.20.tgz#3735a72ec09877b998f04c006af94f86575e4d7d"
|
||||
integrity sha512-MBUu2Q+pzdTBWclPe7AwmRUMTUL0R99ONa8Hswpb987fXgFUdN4XBNBcEa5zy/l2UrIJK+9FUN1jjedZlxgP2A==
|
||||
|
||||
esbuild-linux-ppc64le@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.10.tgz#becd965bfe3425d41e026f1c4678b393127fecbd"
|
||||
integrity sha512-vsOWZjm0rZix7HSmqwPph9arRVCyPtUpcURdayQDuIhMG2/UxJxpbdRaa//w4zYqcJzAWwuyH2PAlyy0ZNuxqQ==
|
||||
esbuild-linux-ppc64le@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.20.tgz#bf58bb6e9d2bfb67a61c09297cf73c3a7116935d"
|
||||
integrity sha512-xkYjQtITA6q/b+/5aAf5n2L063pOxLyXUIad+zYT8GpZh0Sa7aSn18BmrFa2fHb0QSGgTEeRfYkTcBGgoPDjBA==
|
||||
|
||||
esbuild-linux-s390x@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.10.tgz#cc4228ac842febc48b84757814bed964a619be62"
|
||||
integrity sha512-knArKKZm0ypIYWOWyOT7+accVwbVV1LZnl2FWWy05u9Tyv5oqJ2F5+X2Vqe/gqd61enJXQWqoufXopvG3zULOg==
|
||||
esbuild-linux-s390x@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.20.tgz#202699f42e5a7a77ebbf526953f6bbfb2cc68016"
|
||||
integrity sha512-AAcj3x80TXIedpNVuZgjYNETXr2iciOBQv5pGdNGAy6rv7k6Y6sT6SXQ58l2LH2AHbaeTPQjze+Y6qgX1efzrA==
|
||||
|
||||
esbuild-loader@^2.18.0:
|
||||
version "2.18.0"
|
||||
@@ -10957,59 +10962,59 @@ esbuild-loader@^2.18.0:
|
||||
tapable "^2.2.0"
|
||||
webpack-sources "^2.2.0"
|
||||
|
||||
esbuild-netbsd-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.10.tgz#6ec50d9e4547a7579f447307b19f66bbedfd868b"
|
||||
integrity sha512-6Gg8neVcLeyq0yt9bZpReb8ntZ8LBEjthxrcYWVrBElcltnDjIy1hrzsujt0+sC2rL+TlSsE9dzgyuvlDdPp2w==
|
||||
esbuild-netbsd-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.20.tgz#fb133b9726b8e672a7df57629fdc71606952d37c"
|
||||
integrity sha512-30GQKCnsID1WddUi6tr5HFUxJD0t7Uitf6tO9Cf1WqF6C44pf8EflwrhyDFmUyvkddlyfb4OrYI6NNLC/G3ajg==
|
||||
|
||||
esbuild-openbsd-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.10.tgz#925ac3d2326cc219d514e1ca806e80e5143aa043"
|
||||
integrity sha512-9rkHZzp10zI90CfKbFrwmQjqZaeDmyQ6s9/hvCwRkbOCHuto6RvMYH9ghQpcr5cUxD5OQIA+sHXi0zokRNXjcg==
|
||||
esbuild-openbsd-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.20.tgz#50e879a09bb465cda8c9a2f03ba5c2096848c7a1"
|
||||
integrity sha512-zVrf8fY46BK57AkxDdqu2S8TV3p7oLmYIiW707IOHrveI0TwJ2iypAxnwOQuCvowM3UWqVBO2RDBzV7S7t0klg==
|
||||
|
||||
esbuild-sunos-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.10.tgz#8d3576d8cac5c4f9f2a84be81b9078d424dbc739"
|
||||
integrity sha512-mEU+pqkhkhbwpJj5DiN3vL0GUFR/yrL3qj8ER1amIVyRibKbj02VM1QaIuk1sy5DRVIKiFXXgCaHvH3RNWCHIw==
|
||||
esbuild-sunos-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.20.tgz#cb1c55c86513226296935a9bc97fe9457b2a2de4"
|
||||
integrity sha512-MYRsS1O7+aBr2T/0aA4OJrju6eMku4rm81fwGF1KLFwmymIpPGmj7n69n5JW3NKyW5j+FBt0GcyDh9nEnUL1FQ==
|
||||
|
||||
esbuild-windows-32@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.10.tgz#8a67fca4cb594a340566d66eef3f568f65057a48"
|
||||
integrity sha512-Z5DieUL1N6s78dOSdL95KWf8Y89RtPGxIoMF+LEy8ChDsX+pZpz6uAVCn+YaWpqQXO+2TnrcbgBIoprq2Mco1g==
|
||||
esbuild-windows-32@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.20.tgz#5e4db2758408e148e225f06c7724853386916c70"
|
||||
integrity sha512-7VqDITqTU65LQ1Uka/4jx4sUIZc1L8NPlvc7HBRdR15TUyPxmHRQaxMGXd8aakI1FEBcImpJ9SQ4JLmPwRlS1w==
|
||||
|
||||
esbuild-windows-64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.10.tgz#5e6d7c475ff6a71ad0aa4046894364e6c40a9249"
|
||||
integrity sha512-LE5Mm62y0Bilu7RDryBhHIX8rK3at5VwJ6IGM3BsASidCfOBTzqcs7Yy0/Vkq39VKeTmy9/66BAfVoZRNznoDw==
|
||||
esbuild-windows-64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.20.tgz#0731564e8396091b2ac487fb266c86a2bdd45b37"
|
||||
integrity sha512-q4GxY4m5+nXSgqCKx6Cc5pavnhd2g5mHn+K8kNdfCMZsWPDlHLMRjYF5NVQ3/5mJ1M7iR3/Ai4ISjxmsCeGOGA==
|
||||
|
||||
esbuild-windows-arm64@0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.10.tgz#50ab9a83f6ccf71c272e58489ecc4d7375075f32"
|
||||
integrity sha512-OJOyxDtabvcUYTc+O4dR0JMzLBz6G9+gXIHA7Oc5d5Fv1xiYa0nUeo8+W5s2e6ZkPRdIwOseYoL70rZz80S5BA==
|
||||
esbuild-windows-arm64@0.14.20:
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.20.tgz#615978735d3a8b5d0a8e4c35d5a18c0733920d4d"
|
||||
integrity sha512-vOxfU7YwuBMjsUNUygMBhC8T60aCzeYptnHu4k7azqqOVo5EAyoueyWSkFR5GpX6bae5cXyB0vcOV/bfwqRwAg==
|
||||
|
||||
esbuild@^0.14.1, esbuild@^0.14.10, esbuild@^0.14.6:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.10.tgz#10268d2b576b25ed6f8554553413988628a7767b"
|
||||
integrity sha512-ibZb+NwFqBwHHJlpnFMtg4aNmVK+LUtYMFC9CuKs6lDCBEvCHpqCFZFEirpqt1jOugwKGx8gALNGvX56lQyfew==
|
||||
version "0.14.20"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.20.tgz#e83fcb838463f220e864141752bb0f91bfc9c33a"
|
||||
integrity sha512-7aRJRnTjHZ6rFEre52tsAYZxatVELSA/QvYGUBf1iOsYKCnSJICE5seugQFFJgV1Gyl0/mngxQPhxBIqgYG2BA==
|
||||
optionalDependencies:
|
||||
esbuild-android-arm64 "0.14.10"
|
||||
esbuild-darwin-64 "0.14.10"
|
||||
esbuild-darwin-arm64 "0.14.10"
|
||||
esbuild-freebsd-64 "0.14.10"
|
||||
esbuild-freebsd-arm64 "0.14.10"
|
||||
esbuild-linux-32 "0.14.10"
|
||||
esbuild-linux-64 "0.14.10"
|
||||
esbuild-linux-arm "0.14.10"
|
||||
esbuild-linux-arm64 "0.14.10"
|
||||
esbuild-linux-mips64le "0.14.10"
|
||||
esbuild-linux-ppc64le "0.14.10"
|
||||
esbuild-linux-s390x "0.14.10"
|
||||
esbuild-netbsd-64 "0.14.10"
|
||||
esbuild-openbsd-64 "0.14.10"
|
||||
esbuild-sunos-64 "0.14.10"
|
||||
esbuild-windows-32 "0.14.10"
|
||||
esbuild-windows-64 "0.14.10"
|
||||
esbuild-windows-arm64 "0.14.10"
|
||||
esbuild-android-arm64 "0.14.20"
|
||||
esbuild-darwin-64 "0.14.20"
|
||||
esbuild-darwin-arm64 "0.14.20"
|
||||
esbuild-freebsd-64 "0.14.20"
|
||||
esbuild-freebsd-arm64 "0.14.20"
|
||||
esbuild-linux-32 "0.14.20"
|
||||
esbuild-linux-64 "0.14.20"
|
||||
esbuild-linux-arm "0.14.20"
|
||||
esbuild-linux-arm64 "0.14.20"
|
||||
esbuild-linux-mips64le "0.14.20"
|
||||
esbuild-linux-ppc64le "0.14.20"
|
||||
esbuild-linux-s390x "0.14.20"
|
||||
esbuild-netbsd-64 "0.14.20"
|
||||
esbuild-openbsd-64 "0.14.20"
|
||||
esbuild-sunos-64 "0.14.20"
|
||||
esbuild-windows-32 "0.14.20"
|
||||
esbuild-windows-64 "0.14.20"
|
||||
esbuild-windows-arm64 "0.14.20"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
@@ -11442,7 +11447,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
||||
md5.js "^1.3.4"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
"example-app@file:packages/app":
|
||||
"example-app@link:packages/app":
|
||||
version "0.2.63"
|
||||
dependencies:
|
||||
"@backstage/app-defaults" "^0.1.6"
|
||||
@@ -12166,22 +12171,21 @@ fork-ts-checker-webpack-plugin@^6.5.0:
|
||||
tapable "^1.0.0"
|
||||
|
||||
fork-ts-checker-webpack-plugin@^7.0.0-alpha.8:
|
||||
version "7.0.0-alpha.11"
|
||||
resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.0.0-alpha.11.tgz#aa51ff15f203547ae6052b91fa912371db7557b9"
|
||||
integrity sha512-10Q0sjG24BqIkAEFCb+JP0laM6gYO2+3ZV0lBHQ6kJ0+Ot2TffRFcyNkWQBRQqoyqtrDaHfxnjHJ+uXsMO13Bg==
|
||||
version "7.1.1"
|
||||
resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.1.1.tgz#9806c04f3aecaec8c9e8872806cd1f26d50d92a8"
|
||||
integrity sha512-MgAv1g6+HLXz1iq2AZRfBjDhwBwk1HRSjuhNiMs8ax/3tsFo0rHedKwuE6ok44sJ5F5VgwPZae8gq0wiBAqYRw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.14.5"
|
||||
"@babel/code-frame" "^7.16.7"
|
||||
chalk "^4.1.2"
|
||||
chokidar "^3.5.2"
|
||||
cosmiconfig "^7.0.0"
|
||||
chokidar "^3.5.3"
|
||||
cosmiconfig "^7.0.1"
|
||||
deepmerge "^4.2.2"
|
||||
fs-extra "^10.0.0"
|
||||
glob "^7.1.7"
|
||||
memfs "^3.2.2"
|
||||
memfs "^3.4.1"
|
||||
minimatch "^3.0.4"
|
||||
schema-utils "3.1.1"
|
||||
schema-utils "4.0.0"
|
||||
semver "^7.3.5"
|
||||
tapable "^2.0.0"
|
||||
tapable "^2.2.1"
|
||||
|
||||
form-data-encoder@^1.4.3:
|
||||
version "1.6.0"
|
||||
@@ -13546,6 +13550,13 @@ ignore-walk@^3.0.1, ignore-walk@^3.0.3:
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
ignore-walk@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz#fc840e8346cf88a3a9380c5b17933cd8f4d39fa3"
|
||||
integrity sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
ignore@^3.3.5:
|
||||
version "3.3.10"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
|
||||
@@ -16239,17 +16250,6 @@ log-update@^4.0.0:
|
||||
slice-ansi "^4.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
logform@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz#a3997a05985de2ebd325ae0d166dffc9c6fe6b57"
|
||||
integrity sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ==
|
||||
dependencies:
|
||||
colors "^1.2.1"
|
||||
fecha "^4.2.0"
|
||||
ms "^2.1.1"
|
||||
safe-stable-stringify "^1.1.0"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
logform@^2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.npmjs.org/logform/-/logform-2.3.2.tgz#68babe6a74ab09a1fd15a9b1e6cbc7713d41cb5b"
|
||||
@@ -16701,10 +16701,10 @@ media-typer@0.3.0:
|
||||
vinyl "^2.0.1"
|
||||
vinyl-file "^3.0.0"
|
||||
|
||||
memfs@^3.1.2, memfs@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.npmjs.org/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e"
|
||||
integrity sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q==
|
||||
memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305"
|
||||
integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==
|
||||
dependencies:
|
||||
fs-monkey "1.0.3"
|
||||
|
||||
@@ -18013,6 +18013,16 @@ npm-packlist@^2.1.4:
|
||||
npm-bundled "^1.1.1"
|
||||
npm-normalize-package-bin "^1.0.1"
|
||||
|
||||
npm-packlist@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-3.0.0.tgz#0370df5cfc2fcc8f79b8f42b37798dd9ee32c2a9"
|
||||
integrity sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ==
|
||||
dependencies:
|
||||
glob "^7.1.6"
|
||||
ignore-walk "^4.0.1"
|
||||
npm-bundled "^1.1.1"
|
||||
npm-normalize-package-bin "^1.0.1"
|
||||
|
||||
npm-pick-manifest@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.0.tgz#2befed87b0fce956790f62d32afb56d7539c022a"
|
||||
@@ -20561,7 +20571,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6:
|
||||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
@@ -21386,7 +21396,7 @@ safe-stable-stringify@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a"
|
||||
integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==
|
||||
|
||||
safe-stable-stringify@^2.2.0:
|
||||
safe-stable-stringify@^2.2.0, safe-stable-stringify@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73"
|
||||
integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==
|
||||
@@ -21453,16 +21463,7 @@ schema-utils@2.7.0:
|
||||
ajv "^6.12.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
schema-utils@3.1.1, schema-utils@^3.1.0, schema-utils@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
|
||||
integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.8"
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
schema-utils@^4.0.0:
|
||||
schema-utils@4.0.0, schema-utils@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
|
||||
integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==
|
||||
@@ -21472,6 +21473,15 @@ schema-utils@^4.0.0:
|
||||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.0.0"
|
||||
|
||||
schema-utils@^3.1.0, schema-utils@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
|
||||
integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.8"
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
scoped-regex@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/scoped-regex/-/scoped-regex-2.1.0.tgz#7b9be845d81fd9d21d1ec97c61a0b7cf86d2015f"
|
||||
@@ -22885,10 +22895,10 @@ tapable@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||
|
||||
tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b"
|
||||
integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==
|
||||
tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
||||
|
||||
tar-fs@2.1.1, tar-fs@^2.1.1:
|
||||
version "2.1.1"
|
||||
@@ -22967,7 +22977,7 @@ tdigest@^0.1.1:
|
||||
dependencies:
|
||||
bintrees "1.0.1"
|
||||
|
||||
"techdocs-cli-embedded-app@file:packages/techdocs-cli-embedded-app":
|
||||
"techdocs-cli-embedded-app@link:packages/techdocs-cli-embedded-app":
|
||||
version "0.2.62"
|
||||
dependencies:
|
||||
"@backstage/app-defaults" "^0.1.6"
|
||||
@@ -23404,7 +23414,7 @@ trim-off-newlines@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz#8df24847fcb821b0ab27d58ab6efec9f2fe961a1"
|
||||
integrity sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg==
|
||||
|
||||
triple-beam@^1.2.0, triple-beam@^1.3.0:
|
||||
triple-beam@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
|
||||
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
|
||||
@@ -23657,9 +23667,9 @@ typescript@~4.4.4:
|
||||
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
|
||||
|
||||
typescript@~4.5.2, typescript@~4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
|
||||
version "4.5.5"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
|
||||
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.28"
|
||||
@@ -24642,28 +24652,30 @@ windows-release@^3.1.0:
|
||||
dependencies:
|
||||
execa "^1.0.0"
|
||||
|
||||
winston-transport@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59"
|
||||
integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==
|
||||
winston-transport@^4.4.2:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa"
|
||||
integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==
|
||||
dependencies:
|
||||
readable-stream "^2.3.7"
|
||||
triple-beam "^1.2.0"
|
||||
logform "^2.3.2"
|
||||
readable-stream "^3.6.0"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
winston@^3.2.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"
|
||||
integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==
|
||||
version "3.5.1"
|
||||
resolved "https://registry.npmjs.org/winston/-/winston-3.5.1.tgz#b25cc899d015836dbf8c583dec8c4c4483a0da2e"
|
||||
integrity sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA==
|
||||
dependencies:
|
||||
"@dabh/diagnostics" "^2.0.2"
|
||||
async "^3.1.0"
|
||||
async "^3.2.3"
|
||||
is-stream "^2.0.0"
|
||||
logform "^2.2.0"
|
||||
logform "^2.3.2"
|
||||
one-time "^1.0.0"
|
||||
readable-stream "^3.4.0"
|
||||
safe-stable-stringify "^2.3.1"
|
||||
stack-trace "0.0.x"
|
||||
triple-beam "^1.3.0"
|
||||
winston-transport "^4.4.0"
|
||||
winston-transport "^4.4.2"
|
||||
|
||||
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||
version "1.2.3"
|
||||
|
||||
Reference in New Issue
Block a user