From 34224a0b704469428696cdff754555f60b1b4802 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 2 Feb 2022 16:47:31 +0100 Subject: [PATCH 01/55] scripts/create-release-tag: dispatch release workflows Signed-off-by: Patrik Oldsberg --- .github/workflows/deploy_packages.yml | 2 +- scripts/create-release-tag.js | 76 ++++++++++++++++++++------- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 459f48336e..ed02a313fe 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -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 }} diff --git a/scripts/create-release-tag.js b/scripts/create-release-tag.js index 042b00866a..f898705ce5 100755 --- a/scripts/create-release-tag.js +++ b/scripts/create-release-tag.js @@ -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,58 @@ 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, + }, + }); + + console.log('Dispatching upgrade helper sync'); + await octokit.actions.createWorkflowDispatch({ + owner: 'backstage', + repo: 'upgrade-helper-diff', + workflow_id: 'release.yml', + ref: 'master', + inputs: { + // TODO(Rugvip): Switch this over to use the release version once it's ready + version: require('../packages/create-app/package.json').version, + }, + }); +} + +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 => { From 08fcda13ef74646c35db8ca4c7c9ce7868df3a1c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 3 Feb 2022 20:38:27 +0100 Subject: [PATCH 02/55] auth-backend: make it possible to tweak the cookie configuration logic Signed-off-by: Patrik Oldsberg --- .changeset/breezy-windows-jump.md | 5 +++ .changeset/metal-lions-fix.md | 5 +++ plugins/auth-backend/api-report.md | 15 ++++++- .../src/lib/oauth/OAuthAdapter.test.ts | 44 +------------------ .../src/lib/oauth/OAuthAdapter.ts | 22 +++++----- .../src/lib/oauth/helpers.test.ts | 41 +++++++++++------ plugins/auth-backend/src/lib/oauth/helpers.ts | 16 +++---- plugins/auth-backend/src/providers/index.ts | 1 + plugins/auth-backend/src/providers/types.ts | 18 ++++++++ plugins/auth-backend/src/service/router.ts | 10 ++++- 10 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 .changeset/breezy-windows-jump.md create mode 100644 .changeset/metal-lions-fix.md diff --git a/.changeset/breezy-windows-jump.md b/.changeset/breezy-windows-jump.md new file mode 100644 index 0000000000..0d839614ff --- /dev/null +++ b/.changeset/breezy-windows-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +The `callbackUrl` option of `OAuthAdapter` is now required. diff --git a/.changeset/metal-lions-fix.md b/.changeset/metal-lions-fix.md new file mode 100644 index 0000000000..5d35d3d0d3 --- /dev/null +++ b/.changeset/metal-lions-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Added a new `cookieConfigurer` option to `createRouter` that makes it possible to override the default logic for configuring OAuth provider cookies. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 88b75d99b0..ddbef77766 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -214,6 +214,17 @@ export class CatalogIdentityClient { resolveCatalogMembership(query: MemberClaimQuery): Promise; } +// @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) @@ -665,6 +676,8 @@ export interface RouterOptions { // (undocumented) config: Config; // (undocumented) + cookieConfigurer?: CookieConfigurer; + // (undocumented) database: PluginDatabaseManager; // (undocumented) discovery: PluginEndpointDiscovery; @@ -736,5 +749,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 ``` diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index d1057b19a8..c1130b4270 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.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', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 2e2d26db61..82a43c7c64 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -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, }); } diff --git a/plugins/auth-backend/src/lib/oauth/helpers.test.ts b/plugins/auth-backend/src/lib/oauth/helpers.test.ts index a98b684141..2f79cdef98 100644 --- a/plugins/auth-backend/src/lib/oauth/helpers.test.ts +++ b/plugins/auth-backend/src/lib/oauth/helpers.test.ts @@ -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, }); }); diff --git a/plugins/auth-backend/src/lib/oauth/helpers.ts b/plugins/auth-backend/src/lib/oauth/helpers.ts index 878083e679..eec25696a3 100644 --- a/plugins/auth-backend/src/lib/oauth/helpers.ts +++ b/plugins/auth-backend/src/lib/oauth/helpers.ts @@ -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 }; }; diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 7779b67e04..1207254e11 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -43,6 +43,7 @@ export type { AuthHandlerResult, SignInResolver, SignInInfo, + CookieConfigurer, } from './types'; // These types are needed for a postMessage from the login pop-up diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index b02e8d74b8..f3a1a95919 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -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 = { diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index bdb68929b1..101deb9350 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -34,6 +34,7 @@ import { createOidcRouter, TokenFactory, KeyStores } from '../identity'; import session from 'express-session'; import passport from 'passport'; import { Minimatch } from 'minimatch'; +import { CookieConfigurer } from '../providers/types'; type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -44,6 +45,7 @@ export interface RouterOptions { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; providerFactories?: ProviderFactories; + cookieConfigurer?: CookieConfigurer; } export async function createRouter( @@ -56,6 +58,7 @@ export async function createRouter( database, tokenManager, providerFactories, + cookieConfigurer, } = options; const router = Router(); @@ -111,7 +114,12 @@ export async function createRouter( try { const provider = providerFactory({ providerId, - globalConfig: { baseUrl: authUrl, appUrl, isOriginAllowed }, + globalConfig: { + baseUrl: authUrl, + appUrl, + isOriginAllowed, + cookieConfigurer, + }, config: providersConfig.getConfig(providerId), logger, tokenManager, From 9f19529e4847a1785ceed629266d7f27ac56afc3 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 4 Feb 2022 12:02:09 +0000 Subject: [PATCH 03/55] adds progress bar to the task page.. .. if the task is still loading Signed-off-by: Brian Fletcher --- .../src/components/TaskPage/TaskPage.tsx | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 9f5dad2adc..cb2eeab3b2 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -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'; @@ -294,56 +295,59 @@ export const TaskPage = () => { }; return ( - -
- Task Activity - - } - subtitle={`Activity for task: ${taskId}`} - /> - - {taskNotFound ? ( - - ) : ( -
- - - - - {output && hasLinks(output) && ( - - )} - - + <> + + +
+ Task Activity + + } + subtitle={`Activity for task: ${taskId}`} + /> + + {taskNotFound ? ( + + ) : ( +
+ + + + + {output && hasLinks(output) && ( + + )} + + + + +
+ +
+
- -
- -
-
- -
- )} -
- +
+ )} +
+ + ); }; From 33e139e65248b2133212c9eae6e9878261e6c318 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 4 Feb 2022 12:05:37 +0000 Subject: [PATCH 04/55] adds changeset Signed-off-by: Brian Fletcher --- .changeset/loud-monkeys-explode.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-monkeys-explode.md diff --git a/.changeset/loud-monkeys-explode.md b/.changeset/loud-monkeys-explode.md new file mode 100644 index 0000000000..b17b1e1641 --- /dev/null +++ b/.changeset/loud-monkeys-explode.md @@ -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. From 5d5c9bfb4c8b0f4002c07ffd0e28baaa814f2fed Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 4 Feb 2022 13:11:10 +0100 Subject: [PATCH 05/55] auth-backend: removed cookieConfigurer from router options Signed-off-by: Patrik Oldsberg --- .changeset/metal-lions-fix.md | 2 +- plugins/auth-backend/api-report.md | 2 -- plugins/auth-backend/src/service/router.ts | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.changeset/metal-lions-fix.md b/.changeset/metal-lions-fix.md index 5d35d3d0d3..913098bbf7 100644 --- a/.changeset/metal-lions-fix.md +++ b/.changeset/metal-lions-fix.md @@ -2,4 +2,4 @@ '@backstage/plugin-auth-backend': patch --- -Added a new `cookieConfigurer` option to `createRouter` that makes it possible to override the default logic for configuring OAuth provider cookies. +Added a new `cookieConfigurer` option to `AuthProviderConfig` that makes it possible to override the default logic for configuring OAuth provider cookies. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index ddbef77766..aaacd05815 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -676,8 +676,6 @@ export interface RouterOptions { // (undocumented) config: Config; // (undocumented) - cookieConfigurer?: CookieConfigurer; - // (undocumented) database: PluginDatabaseManager; // (undocumented) discovery: PluginEndpointDiscovery; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 101deb9350..ec1a52ffc9 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -34,7 +34,6 @@ import { createOidcRouter, TokenFactory, KeyStores } from '../identity'; import session from 'express-session'; import passport from 'passport'; import { Minimatch } from 'minimatch'; -import { CookieConfigurer } from '../providers/types'; type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -45,7 +44,6 @@ export interface RouterOptions { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; providerFactories?: ProviderFactories; - cookieConfigurer?: CookieConfigurer; } export async function createRouter( @@ -58,7 +56,6 @@ export async function createRouter( database, tokenManager, providerFactories, - cookieConfigurer, } = options; const router = Router(); @@ -118,7 +115,6 @@ export async function createRouter( baseUrl: authUrl, appUrl, isOriginAllowed, - cookieConfigurer, }, config: providersConfig.getConfig(providerId), logger, From b2db40b700a6da3ca812cb6789b72f8b86f48c50 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 01:23:44 +0100 Subject: [PATCH 06/55] cli: add support for specifying target dir in build options Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/config.ts | 18 +++++++++++------- packages/cli/src/lib/builder/types.ts | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 617e354864..65c4d6c740 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -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'; @@ -37,6 +37,9 @@ export async function makeRollupConfigs( options: BuildOptions, ): Promise { const configs = new Array(); + const targetDir = options.targetDir ?? paths.targetDir; + + const distDir = resolvePath(targetDir, 'dist'); if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) { const output = new Array(); @@ -44,7 +47,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 +56,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 +67,13 @@ export async function makeRollupConfigs( } configs.push({ - input: 'src/index.ts', + input: resolvePath(targetDir, 'src/index.ts'), output, preserveEntrySignatures: 'strict', external: require('module').builtinModules, plugins: [ peerDepsExternal({ + packageJsonPath: resolvePath(targetDir, 'package.json'), includeDependencies: true, }), resolve({ mainFields }), @@ -109,13 +113,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,7 +130,7 @@ export async function makeRollupConfigs( configs.push({ input: typesInput, output: { - file: 'dist/index.d.ts', + file: resolvePath(distDir, 'index.d.ts'), format: 'es', }, plugins: [dts()], diff --git a/packages/cli/src/lib/builder/types.ts b/packages/cli/src/lib/builder/types.ts index a02afef01a..c9233c024e 100644 --- a/packages/cli/src/lib/builder/types.ts +++ b/packages/cli/src/lib/builder/types.ts @@ -21,6 +21,7 @@ export enum Output { } export type BuildOptions = { + targetDir?: string; outputs: Set; minify?: boolean; useApiExtractor?: boolean; From ee4931047470df16e9fa407d48c864caaf729fac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 01:55:02 +0100 Subject: [PATCH 07/55] cli: custom warning handler for rollup + support for log prefix Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/config.ts | 14 +++++++++++++- packages/cli/src/lib/builder/types.ts | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 65c4d6c740..0a214f1a7a 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -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'; @@ -38,6 +38,16 @@ export async function makeRollupConfigs( ): Promise { const configs = new Array(); 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'); @@ -69,6 +79,7 @@ export async function makeRollupConfigs( configs.push({ input: resolvePath(targetDir, 'src/index.ts'), output, + onwarn, preserveEntrySignatures: 'strict', external: require('module').builtinModules, plugins: [ @@ -133,6 +144,7 @@ export async function makeRollupConfigs( file: resolvePath(distDir, 'index.d.ts'), format: 'es', }, + onwarn, plugins: [dts()], }); } diff --git a/packages/cli/src/lib/builder/types.ts b/packages/cli/src/lib/builder/types.ts index c9233c024e..f43d84cd92 100644 --- a/packages/cli/src/lib/builder/types.ts +++ b/packages/cli/src/lib/builder/types.ts @@ -21,6 +21,7 @@ export enum Output { } export type BuildOptions = { + logPrefix?: string; targetDir?: string; outputs: Set; minify?: boolean; From 4917c71d00e0049dd8ed75cb676cec1ee6eefd18 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 11:10:33 +0100 Subject: [PATCH 08/55] cli: allow optimized type definition builds across packages Signed-off-by: Patrik Oldsberg --- .../src/lib/builder/buildTypeDefinitions.ts | 165 ++++++++++-------- 1 file changed, 91 insertions(+), 74 deletions(-) diff --git a/packages/cli/src/lib/builder/buildTypeDefinitions.ts b/packages/cli/src/lib/builder/buildTypeDefinitions.ts index ba67532bdb..6822e22ccc 100644 --- a/packages/cli/src/lib/builder/buildTypeDefinitions.ts +++ b/packages/cli/src/lib/builder/buildTypeDefinitions.ts @@ -71,91 +71,108 @@ function prepareApiExtractor() { return apiExtractor!; } -export async function buildTypeDefinitions() { - const { Extractor, ExtractorConfig } = prepareApiExtractor(); +export async function buildTypeDefinitions( + targetDirs: string[] = [paths.targetDir], +) { + const { Extractor, ExtractorConfig, CompilerState } = prepareApiExtractor(); - const distTypesPackageDir = paths.resolveTargetRoot( - 'dist-types', - relativePath(paths.targetRoot, paths.targetDir), + const packageDirs = targetDirs.map(dir => + relativePath(paths.targetRoot, dir), + ); + const entryPoints = packageDirs.map(dir => + paths.resolveTargetRoot('dist-types', dir, 'src/index.d.ts'), ); - const entryPoint = resolvePath(distTypesPackageDir, '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`, - ); - } + let compilerState; - const extractorConfig = ExtractorConfig.prepare({ - configObject: { - mainEntryPointFilePath: entryPoint, - bundledPackages: [], + for (const packageDir of packageDirs) { + const targetDir = paths.resolveTargetRoot(packageDir); + const targetTypesDir = paths.resolveTargetRoot('dist-types', packageDir); + const entryPoint = resolvePath(targetTypesDir, 'src/index.d.ts'); - compiler: { - skipLibCheck: true, - tsconfigFilePath: paths.resolveTargetRoot('tsconfig.json'), + 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`, + ); + } + + const extractorConfig = ExtractorConfig.prepare({ + configObject: { + mainEntryPointFilePath: entryPoint, + bundledPackages: [], + + 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, }, + configObjectFullPath: targetDir, + packageJsonFullPath: resolvePath(targetDir, 'package.json'), + }); - 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'), - }, + if (!compilerState) { + compilerState = CompilerState.create(extractorConfig, { + additionalEntryPoints: entryPoints, + }); + } - newlineKind: 'lf', + const typescriptDir = paths.resolveTargetRoot('node_modules/typescript'); + const hasTypescript = await fs.pathExists(typescriptDir); + const extractorResult = Extractor.invoke(extractorConfig, { + typescriptCompilerFolder: hasTypescript ? typescriptDir : undefined, + compilerState, + localBuild: false, + showVerboseMessages: false, + showDiagnostics: false, + messageCallback(message) { + message.handled = true; + if (ignoredMessages.has(message.messageId)) { + return; + } - projectFolder: paths.targetDir, - }, - configObjectFullPath: paths.targetDir, - packageJsonFullPath: paths.resolveTarget('package.json'), - }); - - 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; - } - - 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}`; + 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); - } - }, - }); + 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); + } + }, + }); - if (!extractorResult.succeeded) { - throw new Error( - `Type definition build completed with ${extractorResult.errorCount} errors` + - ` and ${extractorResult.warningCount} warnings`, - ); + if (!extractorResult.succeeded) { + throw new Error( + `Type definition build completed with ${extractorResult.errorCount} errors` + + ` and ${extractorResult.warningCount} warnings`, + ); + } } } From 03b39bbcc3c467bd28ee39ea6b787dca5d66fde0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 12:20:27 +0100 Subject: [PATCH 09/55] cli: run API Extractor type builds in worker thread Signed-off-by: Patrik Oldsberg --- .../src/lib/builder/buildTypeDefinitions.ts | 152 +++++++----------- .../lib/builder/buildTypeDefinitionsWorker.ts | 105 ++++++++++++ 2 files changed, 163 insertions(+), 94 deletions(-) create mode 100644 packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts diff --git a/packages/cli/src/lib/builder/buildTypeDefinitions.ts b/packages/cli/src/lib/builder/buildTypeDefinitions.ts index 6822e22ccc..85e54850e4 100644 --- a/packages/cli/src/lib/builder/buildTypeDefinitions.ts +++ b/packages/cli/src/lib/builder/buildTypeDefinitions.ts @@ -16,92 +16,47 @@ 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( targetDirs: string[] = [paths.targetDir], ) { - const { Extractor, ExtractorConfig, CompilerState } = prepareApiExtractor(); - const packageDirs = targetDirs.map(dir => relativePath(paths.targetRoot, dir), ); - const entryPoints = packageDirs.map(dir => - paths.resolveTargetRoot('dist-types', dir, '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) { + 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; + }), ); - let compilerState; - - for (const packageDir of packageDirs) { + const workerConfigs = packageDirs.map(packageDir => { const targetDir = paths.resolveTargetRoot(packageDir); const targetTypesDir = paths.resolveTargetRoot('dist-types', packageDir); - const entryPoint = resolvePath(targetTypesDir, 'src/index.d.ts'); - - 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`, - ); - } - - const extractorConfig = ExtractorConfig.prepare({ + const extractorOptions = { configObject: { - mainEntryPointFilePath: entryPoint, + mainEntryPointFilePath: resolvePath(targetTypesDir, 'src/index.d.ts'), bundledPackages: [], compiler: { @@ -122,24 +77,40 @@ export async function buildTypeDefinitions( }, configObjectFullPath: targetDir, packageJsonFullPath: resolvePath(targetDir, 'package.json'), + }; + return { extractorOptions, targetTypesDir }; + }); + + const typescriptDir = paths.resolveTargetRoot('node_modules/typescript'); + const hasTypescript = await fs.pathExists(typescriptDir); + const typescriptCompilerFolder = hasTypescript ? typescriptDir : undefined; + + const worker = new Worker(`(${buildTypeDefinitionsWorker})()`, { + eval: true, + workerData: { + entryPoints, + workerConfigs, + typescriptCompilerFolder, + }, + }); + + await new Promise((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 (!compilerState) { - compilerState = CompilerState.create(extractorConfig, { - additionalEntryPoints: entryPoints, - }); - } - - const typescriptDir = paths.resolveTargetRoot('node_modules/typescript'); - const hasTypescript = await fs.pathExists(typescriptDir); - const extractorResult = Extractor.invoke(extractorConfig, { - typescriptCompilerFolder: hasTypescript ? typescriptDir : undefined, - compilerState, - localBuild: false, - showVerboseMessages: false, - showDiagnostics: false, - messageCallback(message) { - message.handled = true; if (ignoredMessages.has(message.messageId)) { return; } @@ -165,14 +136,7 @@ export async function buildTypeDefinitions( } else { console.log(text); } - }, + } }); - - if (!extractorResult.succeeded) { - throw new Error( - `Type definition build completed with ${extractorResult.errorCount} errors` + - ` and ${extractorResult.warningCount} warnings`, - ); - } - } + }); } diff --git a/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts b/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts new file mode 100644 index 0000000000..8cb843f58e --- /dev/null +++ b/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts @@ -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' }); + } +} From fe571d2b0652e541819d3ff6c9eef84c088fb234 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 12:31:16 +0100 Subject: [PATCH 10/55] cli: add buildPackages for building multiple packages at once Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/index.ts | 2 +- packages/cli/src/lib/builder/packager.ts | 27 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/builder/index.ts b/packages/cli/src/lib/builder/index.ts index c39d964ade..de6ccac0ae 100644 --- a/packages/cli/src/lib/builder/index.ts +++ b/packages/cli/src/lib/builder/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export { buildPackage } from './packager'; +export { buildPackage, buildPackages } from './packager'; export { Output } from './types'; export type { BuildOptions } from './types'; diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index 96c488fce2..9ef1954677 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -17,7 +17,7 @@ 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'; @@ -116,3 +116,28 @@ export const buildPackage = async (options: BuildOptions) => { await Promise.all(buildTasks); }; + +export const buildPackages = async ( + options: (BuildOptions & { targetDir: string })[], +) => { + const rollupConfigs = await Promise.all(options.map(makeRollupConfigs)); + + await Promise.all( + options.map(({ targetDir }) => fs.remove(resolvePath(targetDir, 'dist'))), + ); + + const buildTasks = rollupConfigs.flat().map(rollupBuild); + + const typeDefinitionTargetDirs = options + .filter( + ({ outputs, useApiExtractor }) => + outputs.has(Output.types) && useApiExtractor, + ) + .map(_ => _.targetDir); + + if (typeDefinitionTargetDirs.length > 0) { + buildTasks.push(buildTypeDefinitions(typeDefinitionTargetDirs)); + } + + await Promise.all(buildTasks); +}; From 336b3101519c32428e73599ec4f41f91fe8ef31a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 12:43:51 +0100 Subject: [PATCH 11/55] cli: add initial experimental repo sub-command with build Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/index.ts | 16 ++++++ packages/cli/src/commands/repo/build.ts | 75 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 packages/cli/src/commands/repo/build.ts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index a33e7d4afe..b7de9ced14 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -25,6 +25,21 @@ const configOption = [ Array(), ] 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 all packages in the project that use the standard backstage build script', + ) + .action(lazy(() => import('./repo/build').then(m => m.command))); +} + export function registerScriptCommand(program: CommanderStatic) { const command = program .command('script [command]', { hidden: true }) @@ -312,6 +327,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); diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts new file mode 100644 index 0000000000..50198772d7 --- /dev/null +++ b/packages/cli/src/commands/repo/build.ts @@ -0,0 +1,75 @@ +/* + * 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 { relative as relativePath } from 'path'; +import { buildPackages, Output } from '../../lib/builder'; +import { PackageGraph } from '../../lib/monorepo'; +import { paths } from '../../lib/paths'; +import { getRoleInfo } from '../../lib/role'; + +const outputMap = { + esm: Output.esm, + cjs: Output.cjs, + types: Output.types, + bundle: undefined, +}; + +export async function command(): Promise { + const packages = await PackageGraph.listTargetPackages(); + + const options = packages.flatMap(pkg => { + const role = pkg.packageJson.backstage?.role; + if (!role) { + console.warn(`Ignored ${pkg.packageJson.name} because it has no role`); + return []; + } + + const roleInfo = getRoleInfo(role); + const outputs = roleInfo.output + .map(output => outputMap[output]) + .filter((x): x is Output => Boolean(x)); + if (outputs.length === 0) { + console.warn(`Ignored ${pkg.packageJson.name} because it has no output`); + return []; + } + + const buildScript = pkg.packageJson.scripts?.build; + if (!buildScript) { + console.warn( + `Ignored ${pkg.packageJson.name} because it has no build script`, + ); + return []; + } + if (!buildScript.startsWith('backstage-cli script build')) { + console.warn( + `Ignored ${pkg.packageJson.name} because it has a custom build script, '${buildScript}'`, + ); + return []; + } + + return { + targetDir: pkg.dir, + outputs: new Set(outputs), + logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `, + // TODO(Rugvip): Use commander to parse the script and grab these instead + minify: buildScript.includes('--minify'), + useApiExtractor: buildScript.includes('--experimental-type-build'), + }; + }); + + await buildPackages(options); +} From f8e529030eeb788ff814b0fe21635e30de932dbf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 18:55:31 +0100 Subject: [PATCH 12/55] cli: added getOutputsForRole utility Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/repo/build.ts | 19 ++++--------------- packages/cli/src/lib/builder/index.ts | 2 +- packages/cli/src/lib/builder/packager.ts | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 50198772d7..7b1e4771ec 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -16,17 +16,9 @@ import chalk from 'chalk'; import { relative as relativePath } from 'path'; -import { buildPackages, Output } from '../../lib/builder'; +import { buildPackages, getOutputsForRole } from '../../lib/builder'; import { PackageGraph } from '../../lib/monorepo'; import { paths } from '../../lib/paths'; -import { getRoleInfo } from '../../lib/role'; - -const outputMap = { - esm: Output.esm, - cjs: Output.cjs, - types: Output.types, - bundle: undefined, -}; export async function command(): Promise { const packages = await PackageGraph.listTargetPackages(); @@ -38,11 +30,8 @@ export async function command(): Promise { return []; } - const roleInfo = getRoleInfo(role); - const outputs = roleInfo.output - .map(output => outputMap[output]) - .filter((x): x is Output => Boolean(x)); - if (outputs.length === 0) { + const outputs = getOutputsForRole(role); + if (outputs.size === 0) { console.warn(`Ignored ${pkg.packageJson.name} because it has no output`); return []; } @@ -63,7 +52,7 @@ export async function command(): Promise { return { targetDir: pkg.dir, - outputs: new Set(outputs), + outputs, logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `, // TODO(Rugvip): Use commander to parse the script and grab these instead minify: buildScript.includes('--minify'), diff --git a/packages/cli/src/lib/builder/index.ts b/packages/cli/src/lib/builder/index.ts index de6ccac0ae..00cc463cfb 100644 --- a/packages/cli/src/lib/builder/index.ts +++ b/packages/cli/src/lib/builder/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export { buildPackage, buildPackages } from './packager'; +export { buildPackage, buildPackages, getOutputsForRole } from './packager'; export { Output } from './types'; export type { BuildOptions } from './types'; diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index 9ef1954677..c4963373fa 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -22,6 +22,7 @@ import { paths } from '../paths'; import { makeRollupConfigs } from './config'; import { BuildOptions, Output } from './types'; import { buildTypeDefinitions } from './buildTypeDefinitions'; +import { getRoleInfo } from '../role'; export function formatErrorMessage(error: any) { let msg = ''; @@ -141,3 +142,21 @@ export const buildPackages = async ( await Promise.all(buildTasks); }; + +export function getOutputsForRole(role: string): Set { + const outputs = new Set(); + + 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; +} From d20f260e6dcb2eba8b3c23f63b16665d97995193 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 18:57:16 +0100 Subject: [PATCH 13/55] cli: tweak buildPackages to use standard BuildOptions Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/packager.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index c4963373fa..2413662a17 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -118,13 +118,14 @@ export const buildPackage = async (options: BuildOptions) => { await Promise.all(buildTasks); }; -export const buildPackages = async ( - options: (BuildOptions & { targetDir: string })[], -) => { +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'))), + options.map(({ targetDir }) => fs.remove(resolvePath(targetDir!, 'dist'))), ); const buildTasks = rollupConfigs.flat().map(rollupBuild); @@ -134,7 +135,7 @@ export const buildPackages = async ( ({ outputs, useApiExtractor }) => outputs.has(Output.types) && useApiExtractor, ) - .map(_ => _.targetDir); + .map(_ => _.targetDir!); if (typeDefinitionTargetDirs.length > 0) { buildTasks.push(buildTypeDefinitions(typeDefinitionTargetDirs)); From d59b90852a6d5893bd8bf4f00046329e2a2e9d93 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 30 Jan 2022 19:24:10 +0100 Subject: [PATCH 14/55] changesets: added changesets for CLI repo command and type worker thread Signed-off-by: Patrik Oldsberg --- .changeset/many-terms-type.md | 5 +++++ .changeset/twenty-colts-applaud.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/many-terms-type.md create mode 100644 .changeset/twenty-colts-applaud.md diff --git a/.changeset/many-terms-type.md b/.changeset/many-terms-type.md new file mode 100644 index 0000000000..fd11d14106 --- /dev/null +++ b/.changeset/many-terms-type.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The experimental types build enabled by `--experimental-type-build` now runs in a separate worker thread. diff --git a/.changeset/twenty-colts-applaud.md b/.changeset/twenty-colts-applaud.md new file mode 100644 index 0000000000..2390e21a6e --- /dev/null +++ b/.changeset/twenty-colts-applaud.md @@ -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. From 437659b92f0a0adb1056bf6a386b9edfbafbbbf2 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 4 Feb 2022 14:59:12 +0100 Subject: [PATCH 15/55] accept JSX Element as additionalInfo prop Signed-off-by: Emma Indal --- .../src/layout/ErrorPage/ErrorPage.test.tsx | 12 ++++++++++++ .../src/layout/ErrorPage/ErrorPage.tsx | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx index a314128bce..12ac0927ed 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { ErrorPage } from './ErrorPage'; +import { Link } from '../../components/Link'; import { renderInTestApp } from '@backstage/test-utils'; describe('', () => { @@ -30,4 +31,15 @@ describe('', () => { ).toBeInTheDocument(); expect(getByTestId('go-back-link')).toBeInTheDocument(); }); + + it('should render with additional information including link', async () => { + const { getByText } = await renderInTestApp( + This is some additional information including a 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'); + }); }); diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index 08c002d849..b8e0193bcf 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -27,7 +27,7 @@ import { MicDrop } from './MicDrop'; interface IErrorPageProps { status: string; statusMessage: string; - additionalInfo?: string; + additionalInfo?: string | JSX.Element; } /** @public */ From f2dfbd3fb06f3fccb4c8c36cd52b469eb9ce6429 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 4 Feb 2022 14:59:41 +0100 Subject: [PATCH 16/55] add changeset Signed-off-by: Emma Indal --- .changeset/khaki-jokes-grab.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/khaki-jokes-grab.md diff --git a/.changeset/khaki-jokes-grab.md b/.changeset/khaki-jokes-grab.md new file mode 100644 index 0000000000..967ed1e0e4 --- /dev/null +++ b/.changeset/khaki-jokes-grab.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Accept JSX Element as additionalInfo property of ErrorPage component From 308efff0ac09d9255786edbac15b218f85165ea9 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 4 Feb 2022 15:11:40 +0100 Subject: [PATCH 17/55] add optional supportUrl property Signed-off-by: Emma Indal --- .../src/layout/ErrorPage/ErrorPage.test.tsx | 22 +++++++++++++++++++ .../src/layout/ErrorPage/ErrorPage.tsx | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx index 12ac0927ed..d6ef392f76 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx @@ -42,4 +42,26 @@ describe('', () => { 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( + , + ); + 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( + , + ); + 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'); + }); }); diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index b8e0193bcf..cae8f95ec8 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -28,6 +28,7 @@ interface IErrorPageProps { status: string; statusMessage: string; additionalInfo?: string | JSX.Element; + supportUrl?: string; } /** @public */ @@ -62,7 +63,7 @@ const useStyles = makeStyles( * */ 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,7 @@ export function ErrorPage(props: IErrorPageProps) { navigate(-1)}> Go back - ... or please contact support if you + ... or please contact support if you think this is a bug. From 0912186d16154c59fedf56a3eeff0096a9fa5828 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 4 Feb 2022 15:17:37 +0100 Subject: [PATCH 18/55] update changelog and prettier fixups Signed-off-by: Emma Indal --- .changeset/khaki-jokes-grab.md | 2 +- .../src/layout/ErrorPage/ErrorPage.test.tsx | 29 +++++++++++++++---- .../src/layout/ErrorPage/ErrorPage.tsx | 3 +- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.changeset/khaki-jokes-grab.md b/.changeset/khaki-jokes-grab.md index 967ed1e0e4..e94a580e58 100644 --- a/.changeset/khaki-jokes-grab.md +++ b/.changeset/khaki-jokes-grab.md @@ -2,4 +2,4 @@ '@backstage/core-components': patch --- -Accept JSX Element as additionalInfo property of ErrorPage component +Adjust ErrorPage to accept optional supportUrl property to override app config and JSX Element as additionalInfo property. diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx index d6ef392f76..57d0ba34a1 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx @@ -34,7 +34,16 @@ describe('', () => { it('should render with additional information including link', async () => { const { getByText } = await renderInTestApp( - This is some additional information including a link} />, + + This is some additional information including{' '} + a link + + } + />, ); expect( getByText(/looks like someone dropped the mic!/i), @@ -45,23 +54,33 @@ describe('', () => { it('should render with default support url if supportUrl is not provided', async () => { const { getByText } = await renderInTestApp( - , + , ); 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'); + 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( - , + , ); 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'); + expect(getByText(/contact support/i)).toHaveAttribute( + 'href', + 'https://error-page-test-support-url.com', + ); }); }); diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index cae8f95ec8..27d884684b 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -89,7 +89,8 @@ export function ErrorPage(props: IErrorPageProps) { navigate(-1)}> Go back - ... or please contact support if you + ... or please{' '} + contact support if you think this is a bug. From 85636955987f84d2fb8b320a684f88d7f1ddb586 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 4 Feb 2022 15:07:24 +0000 Subject: [PATCH 19/55] move the progress bar inside the log viewer Signed-off-by: Brian Fletcher --- .../src/components/TaskPage/TaskPage.tsx | 105 +++++++++--------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index cb2eeab3b2..a5a36d1d40 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -295,59 +295,58 @@ export const TaskPage = () => { }; return ( - <> - - -
- Task Activity - - } - subtitle={`Activity for task: ${taskId}`} - /> - - {taskNotFound ? ( - - ) : ( -
- - - - - {output && hasLinks(output) && ( - - )} - - - - -
- -
-
+ +
+ Task Activity + + } + subtitle={`Activity for task: ${taskId}`} + /> + + {taskNotFound ? ( + + ) : ( +
+ + + + + {output && hasLinks(output) && ( + + )} + + -
- )} -
- - + + {!currentStepId && } + +
+ +
+
+ +
+ )} +
+ ); }; From 22f4ecb0e6b4056f12b4574a4b13a12d4475c6b6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 5 Feb 2022 12:44:58 +0100 Subject: [PATCH 20/55] use link: rather than file: for local dependencies Signed-off-by: Patrik Oldsberg --- .changeset/nasty-socks-exist.md | 14 ++++++++++++++ packages/backend/package.json | 2 +- .../default-app/packages/backend/package.json.hbs | 2 +- packages/techdocs-cli/package.json | 2 +- storybook/package.json | 8 ++++---- yarn.lock | 4 ++-- 6 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 .changeset/nasty-socks-exist.md diff --git a/.changeset/nasty-socks-exist.md b/.changeset/nasty-socks-exist.md new file mode 100644 index 0000000000..6aa3e56a1d --- /dev/null +++ b/.changeset/nasty-socks-exist.md @@ -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'}}", +``` diff --git a/packages/backend/package.json b/packages/backend/package.json index 67c959376c..dda43c5e78 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -60,7 +60,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-promise-router": "^4.1.0", "express-prom-bundle": "^6.3.6", diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 2288b475a9..b27d420820 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -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'}}", diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index a8dab88c55..8d2d952e84 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -40,7 +40,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" diff --git a/storybook/package.json b/storybook/package.json index 3864e04484..ea488e94ac 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -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" } } diff --git a/yarn.lock b/yarn.lock index 792a2ecf15..34e7541942 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11437,7 +11437,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" @@ -22962,7 +22962,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" From b70c186194421975e27af00e14d04e15abbb9633 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 5 Feb 2022 11:18:43 +0100 Subject: [PATCH 21/55] techdocs-cli: unified dev and production build through config detection Signed-off-by: Patrik Oldsberg --- .changeset/warm-beds-flow.md | 5 ++ .../app-config.dev.yaml | 12 ---- .../techdocs-cli-embedded-app/package.json | 3 +- .../src/App.test.tsx | 33 +++++------ .../techdocs-cli-embedded-app/src/App.tsx | 2 + .../techdocs-cli-embedded-app/src/config.ts | 59 +++++++++++++++++++ packages/techdocs-cli/scripts/build.sh | 6 +- packages/techdocs-cli/src/lib/httpServer.ts | 12 +++- 8 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 .changeset/warm-beds-flow.md delete mode 100644 packages/techdocs-cli-embedded-app/app-config.dev.yaml create mode 100644 packages/techdocs-cli-embedded-app/src/config.ts diff --git a/.changeset/warm-beds-flow.md b/.changeset/warm-beds-flow.md new file mode 100644 index 0000000000..7d34336739 --- /dev/null +++ b/.changeset/warm-beds-flow.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +Updated the HTTP server to allow for simplification of the development of the CLI itself. diff --git a/packages/techdocs-cli-embedded-app/app-config.dev.yaml b/packages/techdocs-cli-embedded-app/app-config.dev.yaml deleted file mode 100644 index 02d68c940e..0000000000 --- a/packages/techdocs-cli-embedded-app/app-config.dev.yaml +++ /dev/null @@ -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 diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 6eb3119b06..2df050ef86 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -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", diff --git a/packages/techdocs-cli-embedded-app/src/App.test.tsx b/packages/techdocs-cli-embedded-app/src/App.test.tsx index f177433d95..75658d271e 100644 --- a/packages/techdocs-cli-embedded-app/src/App.test.tsx +++ b/packages/techdocs-cli-embedded-app/src/App.test.tsx @@ -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(); - expect(rendered.baseElement).toBeInTheDocument(); + expect(rendered.getByText('Docs Preview')).toBeInTheDocument(); }); }); diff --git a/packages/techdocs-cli-embedded-app/src/App.tsx b/packages/techdocs-cli-embedded-app/src/App.tsx index 00b232d8e1..51bdfbbb11 100644 --- a/packages/techdocs-cli-embedded-app/src/App.tsx +++ b/packages/techdocs-cli-embedded-app/src/App.tsx @@ -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), }); diff --git a/packages/techdocs-cli-embedded-app/src/config.ts b/packages/techdocs-cli-embedded-app/src/config.ts new file mode 100644 index 0000000000..482ceb41ec --- /dev/null +++ b/packages/techdocs-cli-embedded-app/src/config.ts @@ -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, + }, + ]; +} diff --git a/packages/techdocs-cli/scripts/build.sh b/packages/techdocs-cli/scripts/build.sh index 31c2eae6c3..ef2d625711 100755 --- a/packages/techdocs-cli/scripts/build.sh +++ b/packages/techdocs-cli/scripts/build.sh @@ -31,11 +31,7 @@ compile_and_build_cli() { 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 + yarn workspace techdocs-cli-embedded-app build > /dev/null cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/techdocs-preview-bundle > /dev/null } diff --git a/packages/techdocs-cli/src/lib/httpServer.ts b/packages/techdocs-cli/src/lib/httpServer.ts index 0402ce4e69..92fc5abc87 100644 --- a/packages/techdocs-cli/src/lib/httpServer.ts +++ b/packages/techdocs-cli/src/lib/httpServer.ts @@ -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' }], From 814a3ff48021da2c756fe3bf8455372c2b3f5c53 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 5 Feb 2022 12:15:43 +0100 Subject: [PATCH 22/55] techdocs-cli: switch to embedding app using prepack script Signed-off-by: Patrik Oldsberg --- packages/techdocs-cli/package.json | 6 ++-- .../scripts/{build.sh => prepack.sh} | 20 ++----------- .../techdocs-cli/src/commands/serve/serve.ts | 28 +++++++++++++------ 3 files changed, 27 insertions(+), 27 deletions(-) rename packages/techdocs-cli/scripts/{build.sh => prepack.sh} (60%) diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index a8dab88c55..56e7941d4c 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -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" @@ -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", diff --git a/packages/techdocs-cli/scripts/build.sh b/packages/techdocs-cli/scripts/prepack.sh similarity index 60% rename from packages/techdocs-cli/scripts/build.sh rename to packages/techdocs-cli/scripts/prepack.sh index ef2d625711..fa24b10167 100755 --- a/packages/techdocs-cli/scripts/build.sh +++ b/packages/techdocs-cli/scripts/prepack.sh @@ -20,21 +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..." - yarn workspace techdocs-cli-embedded-app build > /dev/null - 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 -r "$TECHDOCS_CLI_DIR"/dist/embedded-app +cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/embedded-app echo "🏁 Ready!" diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 4d71de3e52..a6a671c2df 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -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 /package `. + 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, From 5b54608615a2ba012fccd5110506b84eba30a358 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 5 Feb 2022 18:47:21 +0100 Subject: [PATCH 23/55] cli: add option to include bundled packages in repo build + parse options Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/build/buildApp.ts | 11 +- .../cli/src/commands/build/buildBackend.ts | 8 +- packages/cli/src/commands/build/command.ts | 3 + packages/cli/src/commands/index.ts | 6 +- packages/cli/src/commands/repo/build.ts | 107 +++++++++++++++++- packages/cli/src/lib/bundler/paths.ts | 29 ++--- packages/cli/src/lib/bundler/types.ts | 2 + 7 files changed, 138 insertions(+), 28 deletions(-) diff --git a/packages/cli/src/commands/build/buildApp.ts b/packages/cli/src/commands/build/buildApp.ts index 3d86d796b7..62d51a0810 100644 --- a/packages/cli/src/commands/build/buildApp.ts +++ b/packages/cli/src/commands/build/buildApp.ts @@ -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 { 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, + statsJsonEnabled: writeStats, ...(await loadCliConfig({ - args: options.configPaths, + args: configPaths, fromPackage: name, })), }); diff --git a/packages/cli/src/commands/build/buildBackend.ts b/packages/cli/src/commands/build/buildBackend.ts index a4d8858cf8..490cc413a4 100644 --- a/packages/cli/src/commands/build/buildBackend.ts +++ b/packages/cli/src/commands/build/buildBackend.ts @@ -19,7 +19,6 @@ 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 { buildPackage, Output } from '../../lib/builder'; @@ -27,12 +26,13 @@ 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,7 +41,7 @@ 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]), skeleton: SKELETON_FILE, diff --git a/packages/cli/src/commands/build/command.ts b/packages/cli/src/commands/build/command.ts index 8c13b515d7..1189883b12 100644 --- a/packages/cli/src/commands/build/command.ts +++ b/packages/cli/src/commands/build/command.ts @@ -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 { 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), }); } diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index b7de9ced14..2211af1dda 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -35,7 +35,11 @@ export function registerRepoCommand(program: CommanderStatic) { command .command('build') .description( - 'Build all packages in the project that use the standard backstage build script', + '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))); } diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 7b1e4771ec..84dbe10fa3 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -15,13 +15,65 @@ */ 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 { paths } from '../../lib/paths'; +import { getRoleInfo } from '../../lib/role'; +import { buildApp } from '../build/buildApp'; +import { buildBackend } from '../build/buildBackend'; -export async function command(): Promise { +function parseScriptOptions( + cmd: Command, + scriptCommandName: string, + args: string[], +) { + let rootCommand = cmd; + while (rootCommand.parent) { + rootCommand = rootCommand.parent; + } + const scriptCommand = rootCommand.commands.find(c => c.name() === 'script')!; + const targetCommand = scriptCommand.commands.find( + c => c.name() === scriptCommandName, + ); + if (!targetCommand) { + throw new Error(`Could not find script command '${scriptCommandName}'`); + } + + const currentOpts = targetCommand._optionValues; + const currentStore = targetCommand._storeOptionsAsProperties; + + const result: Record = {}; + targetCommand._storeOptionsAsProperties = false; + targetCommand._optionValues = result; + + targetCommand.parseOptions(args); + + targetCommand._storeOptionsAsProperties = currentOpts; + targetCommand._optionValues = currentStore; + + return result; +} + +function parseBackstageScript( + cmd: Command, + expectedScript: string, + scriptStr?: string, +) { + const expectedPrefix = `backstage-cli script ${expectedScript}`; + if (!scriptStr || !scriptStr.startsWith(expectedPrefix)) { + return undefined; + } + + const argsStr = scriptStr.slice(expectedPrefix.length).trim(); + return parseScriptOptions(cmd, expectedScript, argsStr.split(' ')); +} + +export async function command(cmd: Command): Promise { const packages = await PackageGraph.listTargetPackages(); + const bundledPackages = new Array(); const options = packages.flatMap(pkg => { const role = pkg.packageJson.backstage?.role; @@ -32,7 +84,13 @@ export async function command(): Promise { const outputs = getOutputsForRole(role); if (outputs.size === 0) { - console.warn(`Ignored ${pkg.packageJson.name} because it has no output`); + if (getRoleInfo(role).output.includes('bundle')) { + bundledPackages.push(pkg); + } else { + console.warn( + `Ignored ${pkg.packageJson.name} because it has no output`, + ); + } return []; } @@ -43,7 +101,9 @@ export async function command(): Promise { ); return []; } - if (!buildScript.startsWith('backstage-cli script build')) { + + const buildOptions = parseBackstageScript(cmd, 'build', buildScript); + if (!buildOptions) { console.warn( `Ignored ${pkg.packageJson.name} because it has a custom build script, '${buildScript}'`, ); @@ -54,11 +114,46 @@ export async function command(): Promise { targetDir: pkg.dir, outputs, logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `, - // TODO(Rugvip): Use commander to parse the script and grab these instead - minify: buildScript.includes('--minify'), - useApiExtractor: buildScript.includes('--experimental-type-build'), + minify: buildOptions.minify, + useApiExtractor: buildOptions.experimentalTypeBuild, }; }); + console.log('Building packages'); await buildPackages(options); + + if (cmd.all) { + const apps = bundledPackages.filter( + pkg => pkg.packageJson.backstage?.role === 'app', + ); + + console.log('Building apps'); + await Promise.all( + apps.map(async pkg => { + const buildOptions = parseBackstageScript( + cmd, + 'build', + pkg.packageJson.scripts?.build, + ); + await buildApp({ + targetDir: pkg.dir, + configPaths: (buildOptions?.config as string[]) ?? [], + writeStats: Boolean(buildOptions?.stats), + }); + }), + ); + + console.log('Building backends'); + const backends = bundledPackages.filter( + pkg => pkg.packageJson.backstage?.role === 'backend', + ); + await Promise.all( + backends.map(async pkg => { + await buildBackend({ + targetDir: pkg.dir, + skipBuildDependencies: true, + }); + }), + ); + } } diff --git a/packages/cli/src/lib/bundler/paths.ts b/packages/cli/src/lib/bundler/paths.ts index d465b3010f..3d4925c66b 100644 --- a/packages/cli/src/lib/bundler/paths.ts +++ b/packages/cli/src/lib/bundler/paths.ts @@ -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, }; diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index eae0452d8d..14e60c8892 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -35,6 +35,8 @@ export type ServeOptions = BundlingPathsOptions & { }; export type BuildOptions = BundlingPathsOptions & { + // Target directory, defaulting to paths.targetDir + targetDir?: string; statsJsonEnabled: boolean; parallel?: ParallelOption; schema?: ConfigSchema; From 9b0f6458b84d8d61cc5cd4a0a58cdc2f47f8268a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 5 Feb 2022 23:51:31 +0100 Subject: [PATCH 24/55] cli: refactored build script parser Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/repo/build.ts | 120 +++++++++++++----------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 84dbe10fa3..e8058887c5 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -25,56 +25,61 @@ import { getRoleInfo } from '../../lib/role'; import { buildApp } from '../build/buildApp'; import { buildBackend } from '../build/buildBackend'; -function parseScriptOptions( - cmd: Command, - scriptCommandName: string, - args: string[], -) { - let rootCommand = cmd; - while (rootCommand.parent) { - rootCommand = rootCommand.parent; - } - const scriptCommand = rootCommand.commands.find(c => c.name() === 'script')!; - const targetCommand = scriptCommand.commands.find( - c => c.name() === scriptCommandName, - ); - if (!targetCommand) { - throw new Error(`Could not find script command '${scriptCommandName}'`); +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; } - const currentOpts = targetCommand._optionValues; - const currentStore = targetCommand._storeOptionsAsProperties; - - const result: Record = {}; - targetCommand._storeOptionsAsProperties = false; - targetCommand._optionValues = result; - - targetCommand.parseOptions(args); - - targetCommand._storeOptionsAsProperties = currentOpts; - targetCommand._optionValues = currentStore; - - return result; -} - -function parseBackstageScript( - cmd: Command, - expectedScript: string, - scriptStr?: string, -) { - const expectedPrefix = `backstage-cli script ${expectedScript}`; - if (!scriptStr || !scriptStr.startsWith(expectedPrefix)) { - return undefined; + // 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; } - const argsStr = scriptStr.slice(expectedPrefix.length).trim(); - return parseScriptOptions(cmd, expectedScript, argsStr.split(' ')); + 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 = {}; + 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 { const packages = await PackageGraph.listTargetPackages(); const bundledPackages = new Array(); + const parseBuildScript = createScriptOptionsParser(cmd, ['script', 'build']); + const options = packages.flatMap(pkg => { const role = pkg.packageJson.backstage?.role; if (!role) { @@ -94,18 +99,10 @@ export async function command(cmd: Command): Promise { return []; } - const buildScript = pkg.packageJson.scripts?.build; - if (!buildScript) { - console.warn( - `Ignored ${pkg.packageJson.name} because it has no build script`, - ); - return []; - } - - const buildOptions = parseBackstageScript(cmd, 'build', buildScript); + const buildOptions = parseBuildScript(pkg.packageJson.scripts?.build); if (!buildOptions) { console.warn( - `Ignored ${pkg.packageJson.name} because it has a custom build script, '${buildScript}'`, + `Ignored ${pkg.packageJson.name} because it does not have a matching build script`, ); return []; } @@ -130,15 +127,17 @@ export async function command(cmd: Command): Promise { console.log('Building apps'); await Promise.all( apps.map(async pkg => { - const buildOptions = parseBackstageScript( - cmd, - 'build', - pkg.packageJson.scripts?.build, - ); + 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), + configPaths: (buildOptions.config as string[]) ?? [], + writeStats: Boolean(buildOptions.stats), }); }), ); @@ -149,6 +148,13 @@ export async function command(cmd: Command): Promise { ); await Promise.all( backends.map(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, From 9b122a780caf35be74c6c9eb1a962524e337cd50 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Sun, 6 Feb 2022 19:22:52 -0800 Subject: [PATCH 25/55] feat: add optional userExpand parameter for ms graph Signed-off-by: Jonah Back --- .changeset/ninety-dancers-bow.md | 5 +++++ .../src/microsoftGraph/config.ts | 6 ++++++ .../src/microsoftGraph/read.ts | 4 ++++ .../src/processors/MicrosoftGraphOrgReaderProcessor.ts | 1 + 4 files changed, 16 insertions(+) create mode 100644 .changeset/ninety-dancers-bow.md diff --git a/.changeset/ninety-dancers-bow.md b/.changeset/ninety-dancers-bow.md new file mode 100644 index 0000000000..dc8bd7b702 --- /dev/null +++ b/.changeset/ninety-dancers-bow.md @@ -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 diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 4d37e24632..c401bc2553 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -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. * diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index b2845ac78d..58e60ab76a 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -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, }); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index 351a4983d9..e0e63d86fc 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -102,6 +102,7 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { client, provider.tenantId, { + userExpand: provider.userExpand, userFilter: provider.userFilter, userGroupMemberFilter: provider.userGroupMemberFilter, groupFilter: provider.groupFilter, From 3cfd0ac71ca4c061871812d36d6452b7c0a4508f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 04:36:42 +0000 Subject: [PATCH 26/55] chore(deps): bump fork-ts-checker-webpack-plugin Bumps [fork-ts-checker-webpack-plugin](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin) from 7.0.0-alpha.11 to 7.1.1. - [Release notes](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/releases) - [Changelog](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/blob/main/CHANGELOG.md) - [Commits](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/compare/v7.0.0-alpha.11...v7.1.1) --- updated-dependencies: - dependency-name: fork-ts-checker-webpack-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 65 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4083a1a58e..63f3e0d43d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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== @@ -8404,10 +8404,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" @@ -12166,22 +12166,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" @@ -16701,10 +16700,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" @@ -21453,16 +21452,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 +21462,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 +22884,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" From 92abfffe84d2623b70d4341a1992dc6dba05644a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 12:18:20 +0100 Subject: [PATCH 27/55] cli: deduplicate no-extraneous-dependencies lint rule Signed-off-by: Patrik Oldsberg --- packages/cli/config/eslint.backend.js | 12 +----------- packages/cli/config/eslint.js | 22 ++++++---------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index a97f26f8da..0aec1c247f 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -58,7 +58,7 @@ module.exports = { 'import/no-extraneous-dependencies': [ 'error', { - devDependencies: false, + devDependencies: ['**/*.test.*', 'src/setupTests.*', 'dev/**'], optionalDependencies: true, peerDependencies: true, bundledDependencies: true, @@ -97,16 +97,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], }, }, diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 781af13577..e81e309f72 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -53,7 +53,12 @@ module.exports = { 'import/no-extraneous-dependencies': [ 'error', { - devDependencies: false, + devDependencies: [ + '**/*.test.*', + '**/*.stories.*', + 'src/setupTests.*', + 'dev/**', + ], optionalDependencies: true, peerDependencies: true, bundledDependencies: true, @@ -101,20 +106,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, - }, - ], - }, - }, ], }; From fae2aee8785686f0d96bb486d2ea35190b1ba35d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 12:23:44 +0100 Subject: [PATCH 28/55] cli: remove no-duplicates lint rule Signed-off-by: Patrik Oldsberg --- .changeset/pretty-glasses-admire.md | 9 +++++++++ packages/cli/config/eslint.backend.js | 1 - packages/cli/config/eslint.js | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/pretty-glasses-admire.md diff --git a/.changeset/pretty-glasses-admire.md b/.changeset/pretty-glasses-admire.md new file mode 100644 index 0000000000..318271ddc9 --- /dev/null +++ b/.changeset/pretty-glasses-admire.md @@ -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' +``` diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index 0aec1c247f..519b388b96 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -54,7 +54,6 @@ 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', { diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index e81e309f72..06f22933e8 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -49,7 +49,6 @@ module.exports = { '@typescript-eslint/no-redeclare': 'error', 'no-undef': 'off', 'import/newline-after-import': 'error', - 'import/no-duplicates': 'warn', 'import/no-extraneous-dependencies': [ 'error', { From d0c71e2aa4f9482b8a210588353391fa2057ab9b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 12:36:36 +0100 Subject: [PATCH 29/55] cli: use node API from ESLint to invoke it rather than a subprocess Signed-off-by: Patrik Oldsberg --- .changeset/three-dolls-fly.md | 5 +++++ packages/cli/src/commands/lint.ts | 30 ++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 .changeset/three-dolls-fly.md diff --git a/.changeset/three-dolls-fly.md b/.changeset/three-dolls-fly.md new file mode 100644 index 0000000000..df711c547d --- /dev/null +++ b/.changeset/three-dolls-fly.md @@ -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. diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 5c3e0a88e3..6db17dbfc4 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -15,19 +15,29 @@ */ import { Command } from 'commander'; -import { run } from '../lib/run'; import { paths } from '../lib/paths'; +import { ESLint } from 'eslint'; + +export default async (cmd: Command) => { + const eslint = new ESLint({ + cwd: paths.targetDir, + fix: cmd.fix, + extensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'], + }); + + const results = await eslint.lintFiles(['.']); -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]), - ]; if (cmd.fix) { - args.push('--fix'); + await ESLint.outputFixes(results); } - await run('eslint', args); + const formatter = await eslint.loadFormatter(cmd.format); + 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); + } }; From 4922f11fe3d35607201b41fcc51e37fe57b7453c Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 7 Feb 2022 14:33:12 +0100 Subject: [PATCH 30/55] update type from JSX.Element or string to ReactNode + add test for string based prop Signed-off-by: Emma Indal --- .../src/layout/ErrorPage/ErrorPage.test.tsx | 16 ++++++++++++++++ .../src/layout/ErrorPage/ErrorPage.tsx | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx index 57d0ba34a1..2efdc5da33 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx @@ -32,6 +32,22 @@ describe('', () => { expect(getByTestId('go-back-link')).toBeInTheDocument(); }); + it('should render with additional information of type string', async () => { + const { getByText } = await renderInTestApp( + , + ); + 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( Date: Mon, 7 Feb 2022 14:35:31 +0100 Subject: [PATCH 31/55] update changeset Signed-off-by: Emma Indal --- .changeset/khaki-jokes-grab.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/khaki-jokes-grab.md b/.changeset/khaki-jokes-grab.md index e94a580e58..31201c1078 100644 --- a/.changeset/khaki-jokes-grab.md +++ b/.changeset/khaki-jokes-grab.md @@ -2,4 +2,4 @@ '@backstage/core-components': patch --- -Adjust ErrorPage to accept optional supportUrl property to override app config and JSX Element as additionalInfo property. +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. From 3d05c1da92984939d7aff5b8a6588c104097f003 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 15:18:46 +0100 Subject: [PATCH 32/55] cli: fix lint invocation Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/lint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 6db17dbfc4..9a91e2e5b8 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -18,14 +18,14 @@ import { Command } from 'commander'; import { paths } from '../lib/paths'; import { ESLint } from 'eslint'; -export default async (cmd: Command) => { +export default async (cmd: Command, cmdArgs: string[]) => { const eslint = new ESLint({ cwd: paths.targetDir, fix: cmd.fix, extensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'], }); - const results = await eslint.lintFiles(['.']); + const results = await eslint.lintFiles(cmdArgs ?? ['.']); if (cmd.fix) { await ESLint.outputFixes(results); From 50a19ff8dda13d19e6d32c4ad33bf1397f70c555 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 15:58:35 +0100 Subject: [PATCH 33/55] cli: make lint path output relative to repo root Signed-off-by: Patrik Oldsberg --- .changeset/metal-clouds-fail.md | 5 +++++ packages/cli/src/commands/lint.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/metal-clouds-fail.md diff --git a/.changeset/metal-clouds-fail.md b/.changeset/metal-clouds-fail.md new file mode 100644 index 0000000000..083984c015 --- /dev/null +++ b/.changeset/metal-clouds-fail.md @@ -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. diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 6db17dbfc4..9b86ea06b3 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -32,6 +32,11 @@ export default async (cmd: Command) => { } 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 From 872fcca2814f33a6df46cb9b29af1da8cd803cd0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 6 Feb 2022 00:36:24 +0100 Subject: [PATCH 34/55] cli: refactor parallelism util + new shared worker util Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/app/build.ts | 4 +- packages/cli/src/commands/backend/bundle.ts | 4 +- packages/cli/src/commands/build/buildApp.ts | 4 +- .../cli/src/commands/build/buildBackend.ts | 4 +- packages/cli/src/commands/versions/bump.ts | 184 +++++++++--------- packages/cli/src/lib/bundler/types.ts | 7 +- packages/cli/src/lib/packager/index.ts | 9 +- packages/cli/src/lib/parallel.test.ts | 159 +++++++++++---- packages/cli/src/lib/parallel.ts | 71 +++++-- 9 files changed, 281 insertions(+), 165 deletions(-) diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 36d910fe5b..67b71632c7 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -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, diff --git a/packages/cli/src/commands/backend/bundle.ts b/packages/cli/src/commands/backend/bundle.ts index 338d5cdaa0..ec5fa0a131 100644 --- a/packages/cli/src/commands/backend/bundle.ts +++ b/packages/cli/src/commands/backend/bundle.ts @@ -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, }); diff --git a/packages/cli/src/commands/build/buildApp.ts b/packages/cli/src/commands/build/buildApp.ts index 62d51a0810..b0959b2d6a 100644 --- a/packages/cli/src/commands/build/buildApp.ts +++ b/packages/cli/src/commands/build/buildApp.ts @@ -17,7 +17,7 @@ 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'; interface BuildAppOptions { @@ -32,7 +32,7 @@ export async function buildApp(options: BuildAppOptions) { await buildBundle({ targetDir, entry: 'src/index', - parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), + parallelism: getEnvironmentParallelism(), statsJsonEnabled: writeStats, ...(await loadCliConfig({ args: configPaths, diff --git a/packages/cli/src/commands/build/buildBackend.ts b/packages/cli/src/commands/build/buildBackend.ts index 490cc413a4..e32b7956db 100644 --- a/packages/cli/src/commands/build/buildBackend.ts +++ b/packages/cli/src/commands/build/buildBackend.ts @@ -19,7 +19,7 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; import tar, { CreateOptions } from 'tar'; import { createDistWorkspace } from '../../lib/packager'; -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'; @@ -43,7 +43,7 @@ export async function buildBackend(options: BuildBackendOptions) { targetDir: tmpDir, buildDependencies: !skipBuildDependencies, buildExcludes: [pkg.name], - parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), + parallelism: getEnvironmentParallelism(), skeleton: SKELETON_FILE, }); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 2c16f324bc..0d7c1cdfae 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -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(); // 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(); - 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( - count: number, - items: IterableIterator, - fn: (item: T) => Promise, -) { - 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()), - ); -} diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 14e60c8892..4d6d2e5c9e 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -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 & { @@ -38,7 +37,7 @@ export type BuildOptions = BundlingPathsOptions & { // Target directory, defaulting to paths.targetDir targetDir?: string; statsJsonEnabled: boolean; - parallel?: ParallelOption; + parallelism?: number; schema?: ConfigSchema; frontendConfig: Config; frontendAppConfigs: AppConfig[]; @@ -47,7 +46,7 @@ export type BuildOptions = BundlingPathsOptions & { export type BackendBundlingOptions = { checksEnabled: boolean; isDev: boolean; - parallel?: ParallelOption; + parallelism?: number; inspectEnabled: boolean; inspectBrkEnabled: boolean; }; diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts index c35e2587b9..40af21bc5e 100644 --- a/packages/cli/src/lib/packager/index.ts +++ b/packages/cli/src/lib/packager/index.ts @@ -24,7 +24,6 @@ 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, @@ -69,9 +68,9 @@ type Options = { buildExcludes?: string[]; /** - * Enable (true/false) or control amount of (number) parallelism in some build steps. + * Controls amount of parallelism in some build steps. */ - parallel?: ParallelOption; + parallelism?: number; /** * If set, creates a skeleton tarball that contains all package.json files @@ -115,8 +114,8 @@ export async function createDistWorkspace( if (toBuild.length > 0) { const scopeArgs = toBuild.flatMap(target => ['--scope', target.name]); const lernaArgs = - options.parallel && Number.isInteger(options.parallel) - ? ['--concurrency', options.parallel.toString()] + options.parallelism && Number.isInteger(options.parallelism) + ? ['--concurrency', options.parallelism.toString()] : []; await run('yarn', ['lerna', ...lernaArgs, 'run', ...scopeArgs, 'build'], { diff --git a/packages/cli/src/lib/parallel.test.ts b/packages/cli/src/lib/parallel.test.ts index c5c33473ff..d851f33f82 100644 --- a/packages/cli/src/lib/parallel.test.ts +++ b/packages/cli/src/lib/parallel.test.ts @@ -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(); + const done = new Array(); + 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(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(); + const done = new Array(); + 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(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]); }); }); diff --git a/packages/cli/src/lib/parallel.ts b/packages/cli/src/lib/parallel.ts index f922295a89..840b409078 100644 --- a/packages/cli/src/lib/parallel.ts +++ b/packages/cli/src/lib/parallel.ts @@ -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 = { + /** + * Decides the number of parallel workers by multiplying + * this with the configured parallelism, which defaults to 4 + */ + parallelismFactor: number; + parallelismSetting?: ParallelismOption; + items: Iterable; + worker: (item: TItem) => Promise; +}; + +export async function runParallelWorkers( + options: ParallelWorkerOptions, +) { + const { parallelismFactor, parallelismSetting, items, worker } = options; + const parallelism = parallelismSetting + ? parseParallelismOption(parallelismSetting) + : getEnvironmentParallelism(); + + const iterator = items[Symbol.iterator](); + + async function pop() { + const el = iterator.next(); + if (el.done) { + return; + } + + await worker(el.value); + await pop(); + } + + return Promise.all( + Array(Math.max(Math.floor(parallelismFactor * parallelism), 1)) + .fill(0) + .map(() => pop()), + ); +} From 88ba8f6282f4649c14e9c2e9af3890db72a30071 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 6 Feb 2022 01:07:01 +0100 Subject: [PATCH 35/55] cli: switch out some unbounded parallelism to use worker helper Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/repo/build.ts | 52 ++++++++++++------------ packages/cli/src/lib/builder/packager.ts | 11 +++-- packages/cli/src/lib/parallel.ts | 8 ++-- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index e8058887c5..9c32386304 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -20,8 +20,9 @@ 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 { getRoleInfo } from '../../lib/role'; +import { detectRoleFromPackage } from '../../lib/role'; import { buildApp } from '../build/buildApp'; import { buildBackend } from '../build/buildBackend'; @@ -76,26 +77,30 @@ function createScriptOptionsParser(anyCmd: Command, commandPath: string[]) { export async function command(cmd: Command): Promise { const packages = await PackageGraph.listTargetPackages(); - const bundledPackages = new Array(); + const apps = new Array(); + const backends = new Array(); const parseBuildScript = createScriptOptionsParser(cmd, ['script', 'build']); const options = packages.flatMap(pkg => { - const role = pkg.packageJson.backstage?.role; + 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) { - if (getRoleInfo(role).output.includes('bundle')) { - bundledPackages.push(pkg); - } else { - console.warn( - `Ignored ${pkg.packageJson.name} because it has no output`, - ); - } + console.warn(`Ignored ${pkg.packageJson.name} because it has no output`); return []; } @@ -120,13 +125,11 @@ export async function command(cmd: Command): Promise { await buildPackages(options); if (cmd.all) { - const apps = bundledPackages.filter( - pkg => pkg.packageJson.backstage?.role === 'app', - ); - console.log('Building apps'); - await Promise.all( - apps.map(async pkg => { + await runParallelWorkers({ + items: apps, + parallelismFactor: 1 / 2, + worker: async pkg => { const buildOptions = parseBuildScript(pkg.packageJson.scripts?.build); if (!buildOptions) { console.warn( @@ -139,15 +142,14 @@ export async function command(cmd: Command): Promise { configPaths: (buildOptions.config as string[]) ?? [], writeStats: Boolean(buildOptions.stats), }); - }), - ); + }, + }); console.log('Building backends'); - const backends = bundledPackages.filter( - pkg => pkg.packageJson.backstage?.role === 'backend', - ); - await Promise.all( - backends.map(async pkg => { + await runParallelWorkers({ + items: backends, + parallelismFactor: 1 / 2, + worker: async pkg => { const buildOptions = parseBuildScript(pkg.packageJson.scripts?.build); if (!buildOptions) { console.warn( @@ -159,7 +161,7 @@ export async function command(cmd: Command): Promise { targetDir: pkg.dir, skipBuildDependencies: true, }); - }), - ); + }, + }); } } diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index 2413662a17..c2256c8417 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -23,6 +23,7 @@ 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 = ''; @@ -128,7 +129,7 @@ export const buildPackages = async (options: BuildOptions[]) => { options.map(({ targetDir }) => fs.remove(resolvePath(targetDir!, 'dist'))), ); - const buildTasks = rollupConfigs.flat().map(rollupBuild); + const buildTasks = rollupConfigs.flat().map(opts => () => rollupBuild(opts)); const typeDefinitionTargetDirs = options .filter( @@ -138,10 +139,14 @@ export const buildPackages = async (options: BuildOptions[]) => { .map(_ => _.targetDir!); if (typeDefinitionTargetDirs.length > 0) { - buildTasks.push(buildTypeDefinitions(typeDefinitionTargetDirs)); + // Make sure this one is started first + buildTasks.unshift(() => buildTypeDefinitions(typeDefinitionTargetDirs)); } - await Promise.all(buildTasks); + await runParallelWorkers({ + items: buildTasks, + worker: async task => task(), + }); }; export function getOutputsForRole(role: string): Set { diff --git a/packages/cli/src/lib/parallel.ts b/packages/cli/src/lib/parallel.ts index 840b409078..e1a56770fd 100644 --- a/packages/cli/src/lib/parallel.ts +++ b/packages/cli/src/lib/parallel.ts @@ -54,9 +54,11 @@ export function getEnvironmentParallelism() { type ParallelWorkerOptions = { /** * Decides the number of parallel workers by multiplying - * this with the configured parallelism, which defaults to 4 + * this with the configured parallelism, which defaults to 4. + * + * Defaults to 1. */ - parallelismFactor: number; + parallelismFactor?: number; parallelismSetting?: ParallelismOption; items: Iterable; worker: (item: TItem) => Promise; @@ -65,7 +67,7 @@ type ParallelWorkerOptions = { export async function runParallelWorkers( options: ParallelWorkerOptions, ) { - const { parallelismFactor, parallelismSetting, items, worker } = options; + const { parallelismFactor = 1, parallelismSetting, items, worker } = options; const parallelism = parallelismSetting ? parseParallelismOption(parallelismSetting) : getEnvironmentParallelism(); From 8c4f24eb23c6440c80138e73de4c8f94043aade3 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 7 Feb 2022 17:43:25 +0100 Subject: [PATCH 36/55] chore: bumping the memory limit of the forchore: bumping the memory limit of the ForkTsCheckerWebpackPlugin as it doesn't work without it for the main repo. Signed-off-by: blam --- packages/cli/src/lib/bundler/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index bd2d27a2b8..3cfcafc005 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -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, From 764ee19029f1abc0458a70f9587c70dcd806b0c0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 17:42:32 +0100 Subject: [PATCH 37/55] cli: switch to looping parallel workers + fix bump test log ordering Signed-off-by: Patrik Oldsberg --- .../cli/src/commands/versions/bump.test.ts | 22 ++++++++--------- packages/cli/src/lib/parallel.ts | 24 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index 2dadd06d1d..ae1a4c4627 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -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!', ]); diff --git a/packages/cli/src/lib/parallel.ts b/packages/cli/src/lib/parallel.ts index e1a56770fd..f8af967d22 100644 --- a/packages/cli/src/lib/parallel.ts +++ b/packages/cli/src/lib/parallel.ts @@ -72,21 +72,19 @@ export async function runParallelWorkers( ? parseParallelismOption(parallelismSetting) : getEnvironmentParallelism(); - const iterator = items[Symbol.iterator](); - - async function pop() { - const el = iterator.next(); - if (el.done) { - return; - } - - await worker(el.value); - await pop(); - } + const sharedIterator = items[Symbol.iterator](); + const sharedIterable = { + [Symbol.iterator]: () => sharedIterator, + }; + const workerCount = Math.max(Math.floor(parallelismFactor * parallelism), 1); return Promise.all( - Array(Math.max(Math.floor(parallelismFactor * parallelism), 1)) + Array(workerCount) .fill(0) - .map(() => pop()), + .map(async () => { + for (const value of sharedIterable) { + await worker(value); + } + }), ); } From 7d2ddae27a1994ffaafb8957dc687ed00f0c482c Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 7 Feb 2022 18:04:28 +0100 Subject: [PATCH 38/55] chore: updated api-report Signed-off-by: blam --- plugins/catalog-backend-module-msgraph/api-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 662056eff1..e93822356b 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -141,6 +141,7 @@ export type MicrosoftGraphProviderConfig = { clientId: string; clientSecret: string; userFilter?: string; + userExpand?: string[]; userGroupMemberFilter?: string; groupFilter?: string; }; @@ -170,6 +171,7 @@ export function readMicrosoftGraphOrg( client: MicrosoftGraphClient, tenantId: string, options: { + userExpand?: string[]; userFilter?: string; userGroupMemberFilter?: string; groupFilter?: string; From 68e1ba360f2ab1f6dfb71317460ec881bf5a491f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 18:11:02 +0100 Subject: [PATCH 39/55] workflows: move upgrade helper dispatch to release manifest workflow Signed-off-by: Patrik Oldsberg --- .github/workflows/sync_release-manifest.yml | 28 +++++++++++++++++++++ scripts/create-release-tag.js | 12 --------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 39e270af16..85c01d0631 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -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, + }, + }); diff --git a/scripts/create-release-tag.js b/scripts/create-release-tag.js index f898705ce5..637467f285 100755 --- a/scripts/create-release-tag.js +++ b/scripts/create-release-tag.js @@ -68,18 +68,6 @@ async function dispatchReleaseWorkflows(octokit, releaseVersion) { version: releaseVersion, }, }); - - console.log('Dispatching upgrade helper sync'); - await octokit.actions.createWorkflowDispatch({ - owner: 'backstage', - repo: 'upgrade-helper-diff', - workflow_id: 'release.yml', - ref: 'master', - inputs: { - // TODO(Rugvip): Switch this over to use the release version once it's ready - version: require('../packages/create-app/package.json').version, - }, - }); } async function main() { From 5ca42462b7e0fd54391c7519f994a764f9d85d75 Mon Sep 17 00:00:00 2001 From: mufaddal motiwala Date: Mon, 7 Feb 2022 22:41:33 +0530 Subject: [PATCH 40/55] export DashboardSnapshotComponent Signed-off-by: mufaddal motiwala --- .changeset/cool-birds-ring.md | 5 +++++ plugins/newrelic-dashboard/src/index.ts | 1 + plugins/newrelic-dashboard/src/plugin.ts | 12 ++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 .changeset/cool-birds-ring.md diff --git a/.changeset/cool-birds-ring.md b/.changeset/cool-birds-ring.md new file mode 100644 index 0000000000..0ecd4a47e9 --- /dev/null +++ b/.changeset/cool-birds-ring.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-newrelic-dashboard': patch +--- + +Export DashboardSnapshotComponent from new-relic-dashboard-plugin diff --git a/plugins/newrelic-dashboard/src/index.ts b/plugins/newrelic-dashboard/src/index.ts index 821ccd4bda..060306524f 100644 --- a/plugins/newrelic-dashboard/src/index.ts +++ b/plugins/newrelic-dashboard/src/index.ts @@ -17,5 +17,6 @@ export { newRelicDashboardPlugin, EntityNewRelicDashboardCard, EntityNewRelicDashboardContent, + DashboardSnapshotComponent, } from './plugin'; export { isNewRelicDashboardAvailable } from './Router'; diff --git a/plugins/newrelic-dashboard/src/plugin.ts b/plugins/newrelic-dashboard/src/plugin.ts index 18397902f7..dc3b18e453 100644 --- a/plugins/newrelic-dashboard/src/plugin.ts +++ b/plugins/newrelic-dashboard/src/plugin.ts @@ -60,3 +60,15 @@ export const EntityNewRelicDashboardCard = newRelicDashboardPlugin.provide( }, }), ); + +export const DashboardSnapshotComponent = newRelicDashboardPlugin.provide( + createComponentExtension({ + name: 'DashboardSnapshotComponent', + component: { + lazy: () => + import( + './components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot' + ).then(m => m.DashboardSnapshot), + }, + }), +); From 22ebeb4575720a88477889322c4843fa9553c686 Mon Sep 17 00:00:00 2001 From: mufaddal motiwala Date: Mon, 7 Feb 2022 23:03:41 +0530 Subject: [PATCH 41/55] API report added Signed-off-by: mufaddal motiwala --- plugins/newrelic-dashboard/api-report.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/newrelic-dashboard/api-report.md b/plugins/newrelic-dashboard/api-report.md index ffdd5a2833..260efb47a6 100644 --- a/plugins/newrelic-dashboard/api-report.md +++ b/plugins/newrelic-dashboard/api-report.md @@ -9,6 +9,21 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { RouteRef } from '@backstage/core-plugin-api'; +// Warning: (ae-missing-release-tag) "DashboardSnapshotComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +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) From 2375ef7c3c5f63be60090cf010540b00118f75d0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 30 Jan 2022 19:41:44 +0100 Subject: [PATCH 42/55] cli: packager, rename index to createDistWorkspace Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/packager/{index.ts => createDistWorkspace.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/cli/src/lib/packager/{index.ts => createDistWorkspace.ts} (100%) diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts similarity index 100% rename from packages/cli/src/lib/packager/index.ts rename to packages/cli/src/lib/packager/createDistWorkspace.ts From 46855aaffa7f958518410b933632c6594bc09fbd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 30 Jan 2022 19:42:00 +0100 Subject: [PATCH 43/55] cli: packager, add back index Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/packager/index.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/cli/src/lib/packager/index.ts diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts new file mode 100644 index 0000000000..75f3fdf71d --- /dev/null +++ b/packages/cli/src/lib/packager/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { createDistWorkspace } from './createDistWorkspace'; From c039c184c570b6fb7b6c5de6c7946f4078cc1fa9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 19:10:03 +0100 Subject: [PATCH 44/55] cli: update createDistWorkspace to inline builds when possible Signed-off-by: Patrik Oldsberg --- .../src/lib/packager/createDistWorkspace.ts | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/lib/packager/createDistWorkspace.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts index 40af21bc5e..bcbcdb29cc 100644 --- a/packages/cli/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/lib/packager/createDistWorkspace.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import chalk from 'chalk'; import fs from 'fs-extra'; import { join as joinPath, @@ -28,8 +29,14 @@ import { dependencies as cliDependencies, devDependencies as cliDevDependencies, } from '../../../package.json'; -import { getPackages } from '@manypkg/get-packages'; 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 = [ @@ -95,7 +102,7 @@ export async function createDistWorkspace( options.targetDir ?? (await fs.mkdtemp(resolvePath(tmpdir(), 'dist-workspace'))); - const { packages } = await getPackages(paths.targetDir); + 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 @@ -110,9 +117,59 @@ export async function createDistWorkspace( 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 toBuild = new Set( + targets.map(_ => _.name).filter(name => !exclude.includes(name)), + ); + + const standardBuilds = new Array(); + const customBuild = new Array(); + + 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()] From a41f50f9705b58e7e86255cebaa2644595efc3aa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 19:01:19 +0100 Subject: [PATCH 45/55] cli: added utility for copying package dist files Signed-off-by: Patrik Oldsberg --- packages/cli/package.json | 2 + .../cli/src/lib/packager/copyPackageDist.ts | 88 +++++++++++++++++++ yarn.lock | 22 +++++ 3 files changed, 112 insertions(+) create mode 100644 packages/cli/src/lib/packager/copyPackageDist.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 41cefe6c53..0f6c521ec4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/cli/src/lib/packager/copyPackageDist.ts b/packages/cli/src/lib/packager/copyPackageDist.ts new file mode 100644 index 0000000000..03ac361006 --- /dev/null +++ b/packages/cli/src/lib/packager/copyPackageDist.ts @@ -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'); +} diff --git a/yarn.lock b/yarn.lock index f1bce4f4fa..abfd54f3c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -13545,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" @@ -18012,6 +18024,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" From f65550a08d85dfea1344cfb9176e7403957371b3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 29 Jan 2022 19:10:33 +0100 Subject: [PATCH 46/55] cli: update createDistWorkspace to use dist move utility when possible Signed-off-by: Patrik Oldsberg --- .../src/lib/packager/createDistWorkspace.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/packager/createDistWorkspace.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts index bcbcdb29cc..90daa4bfec 100644 --- a/packages/cli/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/lib/packager/createDistWorkspace.ts @@ -23,6 +23,7 @@ import { } 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 { @@ -212,10 +213,33 @@ export async function createDistWorkspace( return targetDir; } +const FAST_PACK_SCRIPTS = [ + undefined, + 'backstage-cli prepack', + 'backstage-cli script prepack', +]; + async function moveToDistWorkspace( workspaceDir: string, localPackages: PackageGraphNode[], ): Promise { + 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); @@ -260,12 +284,9 @@ async function moveToDistWorkspace( } } - const unsafePackages = localPackages.filter(p => + const [unsafePackages, safePackages] = partition(slowPackPackages, 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. From b906f98119eb6e0d9e2fef204fdc3ede0b0c8d83 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 30 Jan 2022 19:36:40 +0100 Subject: [PATCH 47/55] changesets: add changeset for dist workspace improvements Signed-off-by: Patrik Oldsberg --- .changeset/tasty-spoons-beg.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tasty-spoons-beg.md diff --git a/.changeset/tasty-spoons-beg.md b/.changeset/tasty-spoons-beg.md new file mode 100644 index 0000000000..651c70e03c --- /dev/null +++ b/.changeset/tasty-spoons-beg.md @@ -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. From 51fe2055df5f2ce2400111aba4fffde9b9683a8c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Feb 2022 18:33:53 +0100 Subject: [PATCH 48/55] cli: fix a spelling Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/packager/createDistWorkspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/lib/packager/createDistWorkspace.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts index 90daa4bfec..15ccda30c2 100644 --- a/packages/cli/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/lib/packager/createDistWorkspace.ts @@ -92,7 +92,7 @@ type Options = { * 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 + * 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( From d9ac082bef8a20a9443bb5b354fddd79645ae07f Mon Sep 17 00:00:00 2001 From: Jahred Hope Date: Tue, 8 Feb 2022 08:33:34 +1100 Subject: [PATCH 49/55] Update link to moved repository - techdocs-cli (#9305) --- docs/features/techdocs/creating-and-publishing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/techdocs/creating-and-publishing.md b/docs/features/techdocs/creating-and-publishing.md index ec27a81653..383c3ae6d2 100644 --- a/docs/features/techdocs/creating-and-publishing.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -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. From d897491df24eba614b124f4e69183e052f37c988 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Feb 2022 04:10:21 +0000 Subject: [PATCH 50/55] chore(deps): bump esbuild from 0.14.10 to 0.14.20 Bumps [esbuild](https://github.com/evanw/esbuild) from 0.14.10 to 0.14.20. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.14.10...v0.14.20) --- updated-dependencies: - dependency-name: esbuild dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 186 +++++++++++++++++++++++++++--------------------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/yarn.lock b/yarn.lock index f1bce4f4fa..c55cd084fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10885,65 +10885,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 +10957,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" From 9067d7052f03976f972616df6c8b933b4fb65ddc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Feb 2022 04:26:03 +0000 Subject: [PATCH 51/55] chore(deps): bump winston from 3.3.3 to 3.5.1 Bumps [winston](https://github.com/winstonjs/winston) from 3.3.3 to 3.5.1. - [Release notes](https://github.com/winstonjs/winston/releases) - [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md) - [Commits](https://github.com/winstonjs/winston/compare/v3.3.3...v3.5.1) --- updated-dependencies: - dependency-name: winston dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 53 ++++++++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/yarn.lock b/yarn.lock index f1bce4f4fa..fce4d1f2f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7300,10 +7300,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" @@ -8792,7 +8792,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== @@ -16238,17 +16238,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" @@ -20560,7 +20549,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== @@ -21385,7 +21374,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== @@ -23403,7 +23392,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== @@ -24641,28 +24630,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" From 9774508111cd6d5cd0eeff8e14a59efc49b17f48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Feb 2022 04:37:32 +0000 Subject: [PATCH 52/55] chore(deps-dev): bump typescript from 4.5.4 to 4.5.5 Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.5.4 to 4.5.5. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v4.5.4...v4.5.5) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f1bce4f4fa..be39273806 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23656,9 +23656,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" From a7abd5f35b5152f0ae9671b90622bdd3ad3cf024 Mon Sep 17 00:00:00 2001 From: mufaddal motiwala Date: Tue, 8 Feb 2022 12:09:31 +0530 Subject: [PATCH 53/55] jsDoc comment added Signed-off-by: mufaddal motiwala --- plugins/newrelic-dashboard/api-report.md | 4 +--- plugins/newrelic-dashboard/src/plugin.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/newrelic-dashboard/api-report.md b/plugins/newrelic-dashboard/api-report.md index 260efb47a6..cff24d90c6 100644 --- a/plugins/newrelic-dashboard/api-report.md +++ b/plugins/newrelic-dashboard/api-report.md @@ -9,9 +9,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { RouteRef } from '@backstage/core-plugin-api'; -// Warning: (ae-missing-release-tag) "DashboardSnapshotComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const DashboardSnapshotComponent: ({ guid, name, diff --git a/plugins/newrelic-dashboard/src/plugin.ts b/plugins/newrelic-dashboard/src/plugin.ts index dc3b18e453..9bfecff18f 100644 --- a/plugins/newrelic-dashboard/src/plugin.ts +++ b/plugins/newrelic-dashboard/src/plugin.ts @@ -60,7 +60,14 @@ 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', From c3868458d8afa82972f21afd8efa7b7a0eef45e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 8 Feb 2022 10:10:08 +0100 Subject: [PATCH 54/55] Remove unnecessary get-port dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/healthy-flies-fold.md | 5 +++++ packages/backend-common/package.json | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/healthy-flies-fold.md diff --git a/.changeset/healthy-flies-fold.md b/.changeset/healthy-flies-fold.md new file mode 100644 index 0000000000..ae42533f8a --- /dev/null +++ b/.changeset/healthy-flies-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Removed unnecessary `get-port` dependency diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b661241623..38c74f021f 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -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", From a06665e272f90901ddcc59167bf014077b3d65a8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 8 Feb 2022 10:12:30 +0100 Subject: [PATCH 55/55] cli: avoid preprack failure if dist/embedded-app doesn't exist Signed-off-by: Patrik Oldsberg --- packages/techdocs-cli/scripts/prepack.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/techdocs-cli/scripts/prepack.sh b/packages/techdocs-cli/scripts/prepack.sh index fa24b10167..6b769de37c 100755 --- a/packages/techdocs-cli/scripts/prepack.sh +++ b/packages/techdocs-cli/scripts/prepack.sh @@ -21,6 +21,6 @@ TECHDOCS_CLI_DIR="$SCRIPT_DIR"/.. TECHDOCS_CLI_EMBEDDED_APP_DIR="$TECHDOCS_CLI_DIR"/../techdocs-cli-embedded-app echo "🚚 Copying embedded app into dist/embedded-app" -rm -r "$TECHDOCS_CLI_DIR"/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!"