@backstage/frontend-defaults 0.0.0-nightly-20240830022837

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # @backstage/frontend-defaults
2
+
3
+ ## 0.0.0-nightly-20240830022837
4
+
5
+ ### Minor Changes
6
+
7
+ - 7c80650: Initial release of this package, which provides a default app setup through the `createApp` function. This replaces the existing `createApp` method from `@backstage/frontend-app-api`.
8
+
9
+ ### Patch Changes
10
+
11
+ - 7d19cd5: Added a new `CreateAppOptions` type for the `createApp` options.
12
+ - 7d19cd5: Added `createPublicSignInApp`, used to creating apps for the public entry point.
13
+ - Updated dependencies
14
+ - @backstage/frontend-plugin-api@0.0.0-nightly-20240830022837
15
+ - @backstage/frontend-app-api@0.0.0-nightly-20240830022837
16
+ - @backstage/plugin-app@0.0.0-nightly-20240830022837
17
+ - @backstage/config@1.2.0
18
+ - @backstage/errors@1.2.4
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @backstage/frontend-defaults
2
+
3
+ **The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is part of is in alpha, and we do not yet recommend using it for production deployments**
4
+
5
+ This package provides the high-level APIs used to create Backstage frontend applications with the default setup. For more information, see the [documentation on how to build Backstage frontend applications](https://backstage.io/docs/frontend-system/building-apps/index).
6
+
7
+ ## Documentation
8
+
9
+ - [Backstage Documentation](https://backstage.io/docs)
@@ -0,0 +1,33 @@
1
+ function defaultConfigLoaderSync(runtimeConfigJson = "__APP_INJECTED_RUNTIME_CONFIG__") {
2
+ const appConfig = process.env.APP_CONFIG;
3
+ if (!appConfig) {
4
+ throw new Error("No static configuration provided");
5
+ }
6
+ if (!Array.isArray(appConfig)) {
7
+ throw new Error("Static configuration has invalid format");
8
+ }
9
+ const configs = appConfig.slice();
10
+ if (runtimeConfigJson !== "__app_injected_runtime_config__".toLocaleUpperCase("en-US")) {
11
+ try {
12
+ const data = JSON.parse(runtimeConfigJson);
13
+ if (Array.isArray(data)) {
14
+ configs.push(...data);
15
+ } else {
16
+ configs.push({ data, context: "env" });
17
+ }
18
+ } catch (error) {
19
+ throw new Error(`Failed to load runtime configuration, ${error}`);
20
+ }
21
+ }
22
+ const windowAppConfig = window.__APP_CONFIG__;
23
+ if (windowAppConfig) {
24
+ configs.push({
25
+ context: "window",
26
+ data: windowAppConfig
27
+ });
28
+ }
29
+ return configs;
30
+ }
31
+
32
+ export { defaultConfigLoaderSync };
33
+ //# sourceMappingURL=defaultConfigLoader.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultConfigLoader.esm.js","sources":["../../../../../core-app-api/src/app/defaultConfigLoader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { AppConfigLoader } from './types';\n\n/**\n * The default config loader, which expects that config is available at compile-time\n * in `process.env.APP_CONFIG`. APP_CONFIG should be an array of config objects as\n * returned by the config loader.\n *\n * It will also load runtime config from the __APP_INJECTED_RUNTIME_CONFIG__ string,\n * which can be rewritten at runtime to contain an additional JSON config object.\n * If runtime config is present, it will be placed first in the config array, overriding\n * other config values.\n *\n * @public\n */\nexport const defaultConfigLoader: AppConfigLoader = async () =>\n defaultConfigLoaderSync();\n\n/** @internal */\nexport function defaultConfigLoaderSync(\n // This string may be replaced at runtime to provide additional config.\n // It should be replaced by a JSON-serialized config object.\n // It's a param so we can test it, but at runtime this will always fall back to default.\n runtimeConfigJson: string = '__APP_INJECTED_RUNTIME_CONFIG__',\n) {\n const appConfig = process.env.APP_CONFIG;\n if (!appConfig) {\n throw new Error('No static configuration provided');\n }\n if (!Array.isArray(appConfig)) {\n throw new Error('Static configuration has invalid format');\n }\n const configs = appConfig.slice() as unknown as AppConfig[];\n\n // Avoiding this string also being replaced at runtime\n if (\n runtimeConfigJson !==\n '__app_injected_runtime_config__'.toLocaleUpperCase('en-US')\n ) {\n try {\n const data = JSON.parse(runtimeConfigJson) as JsonObject;\n if (Array.isArray(data)) {\n configs.push(...data);\n } else {\n configs.push({ data, context: 'env' });\n }\n } catch (error) {\n throw new Error(`Failed to load runtime configuration, ${error}`);\n }\n }\n\n const windowAppConfig = (window as any).__APP_CONFIG__;\n if (windowAppConfig) {\n configs.push({\n context: 'window',\n data: windowAppConfig,\n });\n }\n return configs;\n}\n"],"names":[],"mappings":"AAoCgB,SAAA,uBAAA,CAId,oBAA4B,iCAC5B,EAAA;AACA,EAAM,MAAA,SAAA,GAAY,QAAQ,GAAI,CAAA,UAAA,CAAA;AAC9B,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAM,MAAA,IAAI,MAAM,kCAAkC,CAAA,CAAA;AAAA,GACpD;AACA,EAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,SAAS,CAAG,EAAA;AAC7B,IAAM,MAAA,IAAI,MAAM,yCAAyC,CAAA,CAAA;AAAA,GAC3D;AACA,EAAM,MAAA,OAAA,GAAU,UAAU,KAAM,EAAA,CAAA;AAGhC,EAAA,IACE,iBACA,KAAA,iCAAA,CAAkC,iBAAkB,CAAA,OAAO,CAC3D,EAAA;AACA,IAAI,IAAA;AACF,MAAM,MAAA,IAAA,GAAO,IAAK,CAAA,KAAA,CAAM,iBAAiB,CAAA,CAAA;AACzC,MAAI,IAAA,KAAA,CAAM,OAAQ,CAAA,IAAI,CAAG,EAAA;AACvB,QAAQ,OAAA,CAAA,IAAA,CAAK,GAAG,IAAI,CAAA,CAAA;AAAA,OACf,MAAA;AACL,QAAA,OAAA,CAAQ,IAAK,CAAA,EAAE,IAAM,EAAA,OAAA,EAAS,OAAO,CAAA,CAAA;AAAA,OACvC;AAAA,aACO,KAAO,EAAA;AACd,MAAA,MAAM,IAAI,KAAA,CAAM,CAAyC,sCAAA,EAAA,KAAK,CAAE,CAAA,CAAA,CAAA;AAAA,KAClE;AAAA,GACF;AAEA,EAAA,MAAM,kBAAmB,MAAe,CAAA,cAAA,CAAA;AACxC,EAAA,IAAI,eAAiB,EAAA;AACnB,IAAA,OAAA,CAAQ,IAAK,CAAA;AAAA,MACX,OAAS,EAAA,QAAA;AAAA,MACT,IAAM,EAAA,eAAA;AAAA,KACP,CAAA,CAAA;AAAA,GACH;AACA,EAAO,OAAA,OAAA,CAAA;AACT;;;;"}
@@ -0,0 +1,50 @@
1
+ import { ConfigReader } from '@backstage/config';
2
+
3
+ function createLocalBaseUrl(fullUrl) {
4
+ const url = new URL(fullUrl);
5
+ url.protocol = document.location.protocol;
6
+ url.hostname = document.location.hostname;
7
+ url.port = document.location.port;
8
+ return url.toString().replace(/\/$/, "");
9
+ }
10
+ function overrideBaseUrlConfigs(inputConfigs) {
11
+ const urlConfigReader = ConfigReader.fromConfigs(inputConfigs);
12
+ const appBaseUrl = urlConfigReader.getOptionalString("app.baseUrl");
13
+ const backendBaseUrl = urlConfigReader.getOptionalString("backend.baseUrl");
14
+ let configs = inputConfigs;
15
+ let newBackendBaseUrl = void 0;
16
+ let newAppBaseUrl = void 0;
17
+ if (appBaseUrl && backendBaseUrl) {
18
+ const appOrigin = new URL(appBaseUrl).origin;
19
+ const backendOrigin = new URL(backendBaseUrl).origin;
20
+ if (appOrigin === backendOrigin) {
21
+ const maybeNewBackendBaseUrl = createLocalBaseUrl(backendBaseUrl);
22
+ if (backendBaseUrl !== maybeNewBackendBaseUrl) {
23
+ newBackendBaseUrl = maybeNewBackendBaseUrl;
24
+ }
25
+ }
26
+ }
27
+ if (appBaseUrl) {
28
+ const maybeNewAppBaseUrl = createLocalBaseUrl(appBaseUrl);
29
+ if (appBaseUrl !== maybeNewAppBaseUrl) {
30
+ newAppBaseUrl = maybeNewAppBaseUrl;
31
+ }
32
+ }
33
+ if (newAppBaseUrl || newBackendBaseUrl) {
34
+ configs = configs.concat({
35
+ data: {
36
+ app: newAppBaseUrl && {
37
+ baseUrl: newAppBaseUrl
38
+ },
39
+ backend: newBackendBaseUrl && {
40
+ baseUrl: newBackendBaseUrl
41
+ }
42
+ },
43
+ context: "relative-resolver"
44
+ });
45
+ }
46
+ return configs;
47
+ }
48
+
49
+ export { overrideBaseUrlConfigs };
50
+ //# sourceMappingURL=overrideBaseUrlConfigs.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overrideBaseUrlConfigs.esm.js","sources":["../../../../../core-app-api/src/app/overrideBaseUrlConfigs.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig, ConfigReader } from '@backstage/config';\n\n/**\n * Creates a base URL that uses to the current document origin.\n */\nfunction createLocalBaseUrl(fullUrl: string): string {\n const url = new URL(fullUrl);\n url.protocol = document.location.protocol;\n url.hostname = document.location.hostname;\n url.port = document.location.port;\n return url.toString().replace(/\\/$/, '');\n}\n\n/**\n * If we are able to override the app and backend base URLs to values that\n * match the origin of the current location, then this function returns a\n * new array of app configs that contain the overrides.\n *\n * @internal\n */\nexport function overrideBaseUrlConfigs(inputConfigs: AppConfig[]): AppConfig[] {\n const urlConfigReader = ConfigReader.fromConfigs(inputConfigs);\n\n // In tests we may not have `app.baseUrl` or `backend.baseUrl`, to keep them optional\n const appBaseUrl = urlConfigReader.getOptionalString('app.baseUrl');\n const backendBaseUrl = urlConfigReader.getOptionalString('backend.baseUrl');\n\n let configs = inputConfigs;\n\n let newBackendBaseUrl: string | undefined = undefined;\n let newAppBaseUrl: string | undefined = undefined;\n\n if (appBaseUrl && backendBaseUrl) {\n const appOrigin = new URL(appBaseUrl).origin;\n const backendOrigin = new URL(backendBaseUrl).origin;\n\n if (appOrigin === backendOrigin) {\n const maybeNewBackendBaseUrl = createLocalBaseUrl(backendBaseUrl);\n if (backendBaseUrl !== maybeNewBackendBaseUrl) {\n newBackendBaseUrl = maybeNewBackendBaseUrl;\n }\n }\n }\n\n if (appBaseUrl) {\n const maybeNewAppBaseUrl = createLocalBaseUrl(appBaseUrl);\n if (appBaseUrl !== maybeNewAppBaseUrl) {\n newAppBaseUrl = maybeNewAppBaseUrl;\n }\n }\n\n // Only add the relative config if there is actually data to add.\n if (newAppBaseUrl || newBackendBaseUrl) {\n configs = configs.concat({\n data: {\n app: newAppBaseUrl && {\n baseUrl: newAppBaseUrl,\n },\n backend: newBackendBaseUrl && {\n baseUrl: newBackendBaseUrl,\n },\n },\n context: 'relative-resolver',\n });\n }\n\n return configs;\n}\n"],"names":[],"mappings":";;AAqBA,SAAS,mBAAmB,OAAyB,EAAA;AACnD,EAAM,MAAA,GAAA,GAAM,IAAI,GAAA,CAAI,OAAO,CAAA,CAAA;AAC3B,EAAI,GAAA,CAAA,QAAA,GAAW,SAAS,QAAS,CAAA,QAAA,CAAA;AACjC,EAAI,GAAA,CAAA,QAAA,GAAW,SAAS,QAAS,CAAA,QAAA,CAAA;AACjC,EAAI,GAAA,CAAA,IAAA,GAAO,SAAS,QAAS,CAAA,IAAA,CAAA;AAC7B,EAAA,OAAO,GAAI,CAAA,QAAA,EAAW,CAAA,OAAA,CAAQ,OAAO,EAAE,CAAA,CAAA;AACzC,CAAA;AASO,SAAS,uBAAuB,YAAwC,EAAA;AAC7E,EAAM,MAAA,eAAA,GAAkB,YAAa,CAAA,WAAA,CAAY,YAAY,CAAA,CAAA;AAG7D,EAAM,MAAA,UAAA,GAAa,eAAgB,CAAA,iBAAA,CAAkB,aAAa,CAAA,CAAA;AAClE,EAAM,MAAA,cAAA,GAAiB,eAAgB,CAAA,iBAAA,CAAkB,iBAAiB,CAAA,CAAA;AAE1E,EAAA,IAAI,OAAU,GAAA,YAAA,CAAA;AAEd,EAAA,IAAI,iBAAwC,GAAA,KAAA,CAAA,CAAA;AAC5C,EAAA,IAAI,aAAoC,GAAA,KAAA,CAAA,CAAA;AAExC,EAAA,IAAI,cAAc,cAAgB,EAAA;AAChC,IAAA,MAAM,SAAY,GAAA,IAAI,GAAI,CAAA,UAAU,CAAE,CAAA,MAAA,CAAA;AACtC,IAAA,MAAM,aAAgB,GAAA,IAAI,GAAI,CAAA,cAAc,CAAE,CAAA,MAAA,CAAA;AAE9C,IAAA,IAAI,cAAc,aAAe,EAAA;AAC/B,MAAM,MAAA,sBAAA,GAAyB,mBAAmB,cAAc,CAAA,CAAA;AAChE,MAAA,IAAI,mBAAmB,sBAAwB,EAAA;AAC7C,QAAoB,iBAAA,GAAA,sBAAA,CAAA;AAAA,OACtB;AAAA,KACF;AAAA,GACF;AAEA,EAAA,IAAI,UAAY,EAAA;AACd,IAAM,MAAA,kBAAA,GAAqB,mBAAmB,UAAU,CAAA,CAAA;AACxD,IAAA,IAAI,eAAe,kBAAoB,EAAA;AACrC,MAAgB,aAAA,GAAA,kBAAA,CAAA;AAAA,KAClB;AAAA,GACF;AAGA,EAAA,IAAI,iBAAiB,iBAAmB,EAAA;AACtC,IAAA,OAAA,GAAU,QAAQ,MAAO,CAAA;AAAA,MACvB,IAAM,EAAA;AAAA,QACJ,KAAK,aAAiB,IAAA;AAAA,UACpB,OAAS,EAAA,aAAA;AAAA,SACX;AAAA,QACA,SAAS,iBAAqB,IAAA;AAAA,UAC5B,OAAS,EAAA,iBAAA;AAAA,SACX;AAAA,OACF;AAAA,MACA,OAAS,EAAA,mBAAA;AAAA,KACV,CAAA,CAAA;AAAA,GACH;AAEA,EAAO,OAAA,OAAA,CAAA;AACT;;;;"}
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { stringifyError } from '@backstage/errors';
3
+ import { defaultConfigLoaderSync } from './core-app-api/src/app/defaultConfigLoader.esm.js';
4
+ import { overrideBaseUrlConfigs } from './core-app-api/src/app/overrideBaseUrlConfigs.esm.js';
5
+ import { getAvailableFeatures } from './discovery.esm.js';
6
+ import { ConfigReader } from '@backstage/config';
7
+ import appPlugin from '@backstage/plugin-app';
8
+ import { createSpecializedApp } from '@backstage/frontend-app-api';
9
+
10
+ function createApp(options) {
11
+ let suspenseFallback = options?.loadingComponent;
12
+ if (suspenseFallback === void 0) {
13
+ suspenseFallback = "Loading...";
14
+ }
15
+ async function appLoader() {
16
+ const config = await options?.configLoader?.().then((c) => c.config) ?? ConfigReader.fromConfigs(
17
+ overrideBaseUrlConfigs(defaultConfigLoaderSync())
18
+ );
19
+ const discoveredFeatures = getAvailableFeatures(config);
20
+ const providedFeatures = [];
21
+ for (const entry of options?.features ?? []) {
22
+ if ("load" in entry) {
23
+ try {
24
+ const result = await entry.load({ config });
25
+ providedFeatures.push(...result.features);
26
+ } catch (e) {
27
+ throw new Error(
28
+ `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(
29
+ e
30
+ )}`
31
+ );
32
+ }
33
+ } else {
34
+ providedFeatures.push(entry);
35
+ }
36
+ }
37
+ const app = createSpecializedApp({
38
+ config,
39
+ features: [appPlugin, ...discoveredFeatures, ...providedFeatures],
40
+ bindRoutes: options?.bindRoutes
41
+ }).createRoot();
42
+ return { default: () => app };
43
+ }
44
+ return {
45
+ createRoot() {
46
+ const LazyApp = React.lazy(appLoader);
47
+ return /* @__PURE__ */ React.createElement(React.Suspense, { fallback: suspenseFallback }, /* @__PURE__ */ React.createElement(LazyApp, null));
48
+ }
49
+ };
50
+ }
51
+
52
+ export { createApp };
53
+ //# sourceMappingURL=createApp.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApp.esm.js","sources":["../src/createApp.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { JSX, ReactNode } from 'react';\nimport { ConfigApi } from '@backstage/frontend-plugin-api';\nimport { stringifyError } from '@backstage/errors';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs';\nimport { getAvailableFeatures } from './discovery';\nimport { ConfigReader } from '@backstage/config';\nimport appPlugin from '@backstage/plugin-app';\nimport {\n CreateAppRouteBinder,\n FrontendFeature,\n createSpecializedApp,\n} from '@backstage/frontend-app-api';\n\n/**\n * A source of dynamically loaded frontend features.\n *\n * @public\n */\nexport interface CreateAppFeatureLoader {\n /**\n * Returns name of this loader. suitable for showing to users.\n */\n getLoaderName(): string;\n\n /**\n * Loads a number of features dynamically.\n */\n load(options: { config: ConfigApi }): Promise<{\n features: FrontendFeature[];\n }>;\n}\n\n/**\n * Options for {@link createApp}.\n *\n * @public\n */\nexport interface CreateAppOptions {\n features?: (FrontendFeature | CreateAppFeatureLoader)[];\n configLoader?: () => Promise<{ config: ConfigApi }>;\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n /**\n * The component to render while loading the app (waiting for config, features, etc)\n *\n * Is the text \"Loading...\" by default.\n * If set to \"null\" then no loading fallback component is rendered. *\n */\n loadingComponent?: ReactNode;\n}\n\n/**\n * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index\n *\n * @public\n */\nexport function createApp(options?: CreateAppOptions): {\n createRoot(): JSX.Element;\n} {\n let suspenseFallback = options?.loadingComponent;\n if (suspenseFallback === undefined) {\n suspenseFallback = 'Loading...';\n }\n\n async function appLoader() {\n const config =\n (await options?.configLoader?.().then(c => c.config)) ??\n ConfigReader.fromConfigs(\n overrideBaseUrlConfigs(defaultConfigLoaderSync()),\n );\n\n const discoveredFeatures = getAvailableFeatures(config);\n\n const providedFeatures: FrontendFeature[] = [];\n for (const entry of options?.features ?? []) {\n if ('load' in entry) {\n try {\n const result = await entry.load({ config });\n providedFeatures.push(...result.features);\n } catch (e) {\n throw new Error(\n `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(\n e,\n )}`,\n );\n }\n } else {\n providedFeatures.push(entry);\n }\n }\n\n const app = createSpecializedApp({\n config,\n features: [appPlugin, ...discoveredFeatures, ...providedFeatures],\n bindRoutes: options?.bindRoutes,\n }).createRoot();\n\n return { default: () => app };\n }\n\n return {\n createRoot() {\n const LazyApp = React.lazy(appLoader);\n return (\n <React.Suspense fallback={suspenseFallback}>\n <LazyApp />\n </React.Suspense>\n );\n },\n };\n}\n"],"names":[],"mappings":";;;;;;;;;AA0EO,SAAS,UAAU,OAExB,EAAA;AACA,EAAA,IAAI,mBAAmB,OAAS,EAAA,gBAAA,CAAA;AAChC,EAAA,IAAI,qBAAqB,KAAW,CAAA,EAAA;AAClC,IAAmB,gBAAA,GAAA,YAAA,CAAA;AAAA,GACrB;AAEA,EAAA,eAAe,SAAY,GAAA;AACzB,IAAM,MAAA,MAAA,GACH,MAAM,OAAA,EAAS,YAAe,IAAA,CAAE,KAAK,CAAK,CAAA,KAAA,CAAA,CAAE,MAAM,CAAA,IACnD,YAAa,CAAA,WAAA;AAAA,MACX,sBAAA,CAAuB,yBAAyB,CAAA;AAAA,KAClD,CAAA;AAEF,IAAM,MAAA,kBAAA,GAAqB,qBAAqB,MAAM,CAAA,CAAA;AAEtD,IAAA,MAAM,mBAAsC,EAAC,CAAA;AAC7C,IAAA,KAAA,MAAW,KAAS,IAAA,OAAA,EAAS,QAAY,IAAA,EAAI,EAAA;AAC3C,MAAA,IAAI,UAAU,KAAO,EAAA;AACnB,QAAI,IAAA;AACF,UAAA,MAAM,SAAS,MAAM,KAAA,CAAM,IAAK,CAAA,EAAE,QAAQ,CAAA,CAAA;AAC1C,UAAiB,gBAAA,CAAA,IAAA,CAAK,GAAG,MAAA,CAAO,QAAQ,CAAA,CAAA;AAAA,iBACjC,CAAG,EAAA;AACV,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAiD,8CAAA,EAAA,KAAA,CAAM,aAAc,EAAC,CAAM,GAAA,EAAA,cAAA;AAAA,cAC1E,CAAA;AAAA,aACD,CAAA,CAAA;AAAA,WACH,CAAA;AAAA,SACF;AAAA,OACK,MAAA;AACL,QAAA,gBAAA,CAAiB,KAAK,KAAK,CAAA,CAAA;AAAA,OAC7B;AAAA,KACF;AAEA,IAAA,MAAM,MAAM,oBAAqB,CAAA;AAAA,MAC/B,MAAA;AAAA,MACA,UAAU,CAAC,SAAA,EAAW,GAAG,kBAAA,EAAoB,GAAG,gBAAgB,CAAA;AAAA,MAChE,YAAY,OAAS,EAAA,UAAA;AAAA,KACtB,EAAE,UAAW,EAAA,CAAA;AAEd,IAAO,OAAA,EAAE,OAAS,EAAA,MAAM,GAAI,EAAA,CAAA;AAAA,GAC9B;AAEA,EAAO,OAAA;AAAA,IACL,UAAa,GAAA;AACX,MAAM,MAAA,OAAA,GAAU,KAAM,CAAA,IAAA,CAAK,SAAS,CAAA,CAAA;AACpC,MACE,uBAAA,KAAA,CAAA,aAAA,CAAC,MAAM,QAAN,EAAA,EAAe,UAAU,gBACxB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,aAAQ,CACX,CAAA,CAAA;AAAA,KAEJ;AAAA,GACF,CAAA;AACF;;;;"}
@@ -0,0 +1,57 @@
1
+ import { createFrontendModule, coreExtensionData, useApi, identityApiRef } from '@backstage/frontend-plugin-api';
2
+ import React from 'react';
3
+ import { useAsync, useMountEffect } from '@react-hookz/web';
4
+ import { createApp } from './createApp.esm.js';
5
+ import appPlugin from '@backstage/plugin-app';
6
+
7
+ function InternalCookieAuthRedirect() {
8
+ const identityApi = useApi(identityApiRef);
9
+ const [state, actions] = useAsync(async () => {
10
+ const { token } = await identityApi.getCredentials();
11
+ if (!token) {
12
+ throw new Error("Expected Backstage token in sign-in response");
13
+ }
14
+ return token;
15
+ });
16
+ useMountEffect(actions.execute);
17
+ if (state.status === "error" && state.error) {
18
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, "An error occurred: ", state.error.message);
19
+ }
20
+ if (state.status === "success" && state.result) {
21
+ return /* @__PURE__ */ React.createElement(
22
+ "form",
23
+ {
24
+ ref: (form) => form?.submit(),
25
+ action: window.location.href,
26
+ method: "POST",
27
+ style: { visibility: "hidden" }
28
+ },
29
+ /* @__PURE__ */ React.createElement("input", { type: "hidden", name: "type", value: "sign-in" }),
30
+ /* @__PURE__ */ React.createElement("input", { type: "hidden", name: "token", value: state.result }),
31
+ /* @__PURE__ */ React.createElement("input", { type: "submit", value: "Continue" })
32
+ );
33
+ }
34
+ return null;
35
+ }
36
+ function createPublicSignInApp(options) {
37
+ return createApp({
38
+ ...options,
39
+ features: [
40
+ ...options?.features ?? [],
41
+ // This is a rather than app plugin override in order for it to take precedence over any supplied app plugin override
42
+ createFrontendModule({
43
+ pluginId: "app",
44
+ extensions: [
45
+ appPlugin.getExtension("app/layout").override({
46
+ factory: () => [
47
+ coreExtensionData.reactElement(/* @__PURE__ */ React.createElement(InternalCookieAuthRedirect, null))
48
+ ]
49
+ })
50
+ ]
51
+ })
52
+ ]
53
+ });
54
+ }
55
+
56
+ export { InternalCookieAuthRedirect, createPublicSignInApp };
57
+ //# sourceMappingURL=createPublicSignInApp.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createPublicSignInApp.esm.js","sources":["../src/createPublicSignInApp.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreExtensionData,\n createFrontendModule,\n identityApiRef,\n useApi,\n} from '@backstage/frontend-plugin-api';\nimport React from 'react';\nimport { useAsync, useMountEffect } from '@react-hookz/web';\nimport { CreateAppOptions, createApp } from './createApp';\nimport appPlugin from '@backstage/plugin-app';\n\n// This is a copy of the CookieAuthRedirect component from the auth-react\n// plugin, to avoid a dependency on that package. Long-term we want this to be\n// the only implementation and remove the one in auth-react once the old frontend system is gone.\n\n// TODO(Rugvip): Should this be part of the app plugin instead? since it owns the backend part of it.\n\n/** @internal */\nexport function InternalCookieAuthRedirect() {\n const identityApi = useApi(identityApiRef);\n\n const [state, actions] = useAsync(async () => {\n const { token } = await identityApi.getCredentials();\n if (!token) {\n throw new Error('Expected Backstage token in sign-in response');\n }\n return token;\n });\n\n useMountEffect(actions.execute);\n\n if (state.status === 'error' && state.error) {\n return <>An error occurred: {state.error.message}</>;\n }\n\n if (state.status === 'success' && state.result) {\n return (\n <form\n ref={form => form?.submit()}\n action={window.location.href}\n method=\"POST\"\n style={{ visibility: 'hidden' }}\n >\n <input type=\"hidden\" name=\"type\" value=\"sign-in\" />\n <input type=\"hidden\" name=\"token\" value={state.result} />\n <input type=\"submit\" value=\"Continue\" />\n </form>\n );\n }\n\n return null;\n}\n\n/**\n * Creates an app that is suitable for the public sign-in page, for use in the `index-public-experimental.tsx` file.\n *\n * @remarks\n *\n * This app has an override for the `app/layout` extension, which means that\n * most extension typically installed in an app will be ignored. However, you\n * can still for example install API and root element extensions.\n *\n * A typical setup of this app will only install a custom sign-in page.\n *\n * @example\n * ```ts\n * const app = createPublicSignInApp({\n * features: [signInPageModule],\n * });\n * ```\n *\n * @public\n */\nexport function createPublicSignInApp(options?: CreateAppOptions) {\n return createApp({\n ...options,\n features: [\n ...(options?.features ?? []),\n // This is a rather than app plugin override in order for it to take precedence over any supplied app plugin override\n createFrontendModule({\n pluginId: 'app',\n extensions: [\n appPlugin.getExtension('app/layout').override({\n factory: () => [\n coreExtensionData.reactElement(<InternalCookieAuthRedirect />),\n ],\n }),\n ],\n }),\n ],\n });\n}\n"],"names":[],"mappings":";;;;;;AAkCO,SAAS,0BAA6B,GAAA;AAC3C,EAAM,MAAA,WAAA,GAAc,OAAO,cAAc,CAAA,CAAA;AAEzC,EAAA,MAAM,CAAC,KAAA,EAAO,OAAO,CAAA,GAAI,SAAS,YAAY;AAC5C,IAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,YAAY,cAAe,EAAA,CAAA;AACnD,IAAA,IAAI,CAAC,KAAO,EAAA;AACV,MAAM,MAAA,IAAI,MAAM,8CAA8C,CAAA,CAAA;AAAA,KAChE;AACA,IAAO,OAAA,KAAA,CAAA;AAAA,GACR,CAAA,CAAA;AAED,EAAA,cAAA,CAAe,QAAQ,OAAO,CAAA,CAAA;AAE9B,EAAA,IAAI,KAAM,CAAA,MAAA,KAAW,OAAW,IAAA,KAAA,CAAM,KAAO,EAAA;AAC3C,IAAA,uBAAS,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAA,qBAAA,EAAoB,KAAM,CAAA,KAAA,CAAM,OAAQ,CAAA,CAAA;AAAA,GACnD;AAEA,EAAA,IAAI,KAAM,CAAA,MAAA,KAAW,SAAa,IAAA,KAAA,CAAM,MAAQ,EAAA;AAC9C,IACE,uBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,CAAQ,IAAA,KAAA,IAAA,EAAM,MAAO,EAAA;AAAA,QAC1B,MAAA,EAAQ,OAAO,QAAS,CAAA,IAAA;AAAA,QACxB,MAAO,EAAA,MAAA;AAAA,QACP,KAAA,EAAO,EAAE,UAAA,EAAY,QAAS,EAAA;AAAA,OAAA;AAAA,0CAE7B,OAAM,EAAA,EAAA,IAAA,EAAK,UAAS,IAAK,EAAA,MAAA,EAAO,OAAM,SAAU,EAAA,CAAA;AAAA,sBACjD,KAAA,CAAA,aAAA,CAAC,WAAM,IAAK,EAAA,QAAA,EAAS,MAAK,OAAQ,EAAA,KAAA,EAAO,MAAM,MAAQ,EAAA,CAAA;AAAA,sBACtD,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAM,IAAK,EAAA,QAAA,EAAS,OAAM,UAAW,EAAA,CAAA;AAAA,KACxC,CAAA;AAAA,GAEJ;AAEA,EAAO,OAAA,IAAA,CAAA;AACT,CAAA;AAsBO,SAAS,sBAAsB,OAA4B,EAAA;AAChE,EAAA,OAAO,SAAU,CAAA;AAAA,IACf,GAAG,OAAA;AAAA,IACH,QAAU,EAAA;AAAA,MACR,GAAI,OAAS,EAAA,QAAA,IAAY,EAAC;AAAA;AAAA,MAE1B,oBAAqB,CAAA;AAAA,QACnB,QAAU,EAAA,KAAA;AAAA,QACV,UAAY,EAAA;AAAA,UACV,SAAU,CAAA,YAAA,CAAa,YAAY,CAAA,CAAE,QAAS,CAAA;AAAA,YAC5C,SAAS,MAAM;AAAA,cACb,iBAAkB,CAAA,YAAA,iBAAc,KAAA,CAAA,aAAA,CAAA,0BAAA,EAAA,IAA2B,CAAE,CAAA;AAAA,aAC/D;AAAA,WACD,CAAA;AAAA,SACH;AAAA,OACD,CAAA;AAAA,KACH;AAAA,GACD,CAAA,CAAA;AACH;;;;"}
@@ -0,0 +1,56 @@
1
+ import { ConfigReader } from '@backstage/config';
2
+
3
+ function readPackageDetectionConfig(config) {
4
+ const packages = config.getOptional("app.experimental.packages");
5
+ if (packages === void 0 || packages === null) {
6
+ return void 0;
7
+ }
8
+ if (typeof packages === "string") {
9
+ if (packages !== "all") {
10
+ throw new Error(
11
+ `Invalid app.experimental.packages mode, got '${packages}', expected 'all'`
12
+ );
13
+ }
14
+ return {};
15
+ }
16
+ if (typeof packages !== "object" || Array.isArray(packages)) {
17
+ throw new Error(
18
+ "Invalid config at 'app.experimental.packages', expected object"
19
+ );
20
+ }
21
+ const packagesConfig = new ConfigReader(
22
+ packages,
23
+ "app.experimental.packages"
24
+ );
25
+ return {
26
+ include: packagesConfig.getOptionalStringArray("include"),
27
+ exclude: packagesConfig.getOptionalStringArray("exclude")
28
+ };
29
+ }
30
+ function getAvailableFeatures(config) {
31
+ const discovered = window["__@backstage/discovered__"];
32
+ const detection = readPackageDetectionConfig(config);
33
+ if (!detection) {
34
+ return [];
35
+ }
36
+ return discovered?.modules.filter(({ name }) => {
37
+ if (detection.exclude?.includes(name)) {
38
+ return false;
39
+ }
40
+ if (detection.include && !detection.include.includes(name)) {
41
+ return false;
42
+ }
43
+ return true;
44
+ }).map((m) => m.default).filter(isBackstageFeature) ?? [];
45
+ }
46
+ function isBackstageFeature(obj) {
47
+ if (obj !== null && typeof obj === "object" && "$$type" in obj) {
48
+ return obj.$$type === "@backstage/FrontendPlugin" || obj.$$type === "@backstage/FrontendModule" || // TODO: Remove this once the old plugin type and extension overrides
49
+ // are no longer supported
50
+ obj.$$type === "@backstage/BackstagePlugin" || obj.$$type === "@backstage/ExtensionOverrides";
51
+ }
52
+ return false;
53
+ }
54
+
55
+ export { getAvailableFeatures };
56
+ //# sourceMappingURL=discovery.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.esm.js","sources":["../src/discovery.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config, ConfigReader } from '@backstage/config';\nimport { FrontendFeature } from '@backstage/frontend-app-api';\n\ninterface DiscoveryGlobal {\n modules: Array<{ name: string; export?: string; default: unknown }>;\n}\n\nfunction readPackageDetectionConfig(config: Config) {\n const packages = config.getOptional('app.experimental.packages');\n if (packages === undefined || packages === null) {\n return undefined;\n }\n\n if (typeof packages === 'string') {\n if (packages !== 'all') {\n throw new Error(\n `Invalid app.experimental.packages mode, got '${packages}', expected 'all'`,\n );\n }\n return {};\n }\n\n if (typeof packages !== 'object' || Array.isArray(packages)) {\n throw new Error(\n \"Invalid config at 'app.experimental.packages', expected object\",\n );\n }\n const packagesConfig = new ConfigReader(\n packages,\n 'app.experimental.packages',\n );\n\n return {\n include: packagesConfig.getOptionalStringArray('include'),\n exclude: packagesConfig.getOptionalStringArray('exclude'),\n };\n}\n\n/**\n * @internal\n */\nexport function getAvailableFeatures(config: Config): FrontendFeature[] {\n const discovered = (\n window as { '__@backstage/discovered__'?: DiscoveryGlobal }\n )['__@backstage/discovered__'];\n\n const detection = readPackageDetectionConfig(config);\n if (!detection) {\n return [];\n }\n\n return (\n discovered?.modules\n .filter(({ name }) => {\n if (detection.exclude?.includes(name)) {\n return false;\n }\n if (detection.include && !detection.include.includes(name)) {\n return false;\n }\n return true;\n })\n .map(m => m.default)\n .filter(isBackstageFeature) ?? []\n );\n}\n\nfunction isBackstageFeature(obj: unknown): obj is FrontendFeature {\n if (obj !== null && typeof obj === 'object' && '$$type' in obj) {\n return (\n obj.$$type === '@backstage/FrontendPlugin' ||\n obj.$$type === '@backstage/FrontendModule' ||\n // TODO: Remove this once the old plugin type and extension overrides\n // are no longer supported\n obj.$$type === '@backstage/BackstagePlugin' ||\n obj.$$type === '@backstage/ExtensionOverrides'\n );\n }\n return false;\n}\n"],"names":[],"mappings":";;AAuBA,SAAS,2BAA2B,MAAgB,EAAA;AAClD,EAAM,MAAA,QAAA,GAAW,MAAO,CAAA,WAAA,CAAY,2BAA2B,CAAA,CAAA;AAC/D,EAAI,IAAA,QAAA,KAAa,KAAa,CAAA,IAAA,QAAA,KAAa,IAAM,EAAA;AAC/C,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACT;AAEA,EAAI,IAAA,OAAO,aAAa,QAAU,EAAA;AAChC,IAAA,IAAI,aAAa,KAAO,EAAA;AACtB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,gDAAgD,QAAQ,CAAA,iBAAA,CAAA;AAAA,OAC1D,CAAA;AAAA,KACF;AACA,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AAEA,EAAA,IAAI,OAAO,QAAa,KAAA,QAAA,IAAY,KAAM,CAAA,OAAA,CAAQ,QAAQ,CAAG,EAAA;AAC3D,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,gEAAA;AAAA,KACF,CAAA;AAAA,GACF;AACA,EAAA,MAAM,iBAAiB,IAAI,YAAA;AAAA,IACzB,QAAA;AAAA,IACA,2BAAA;AAAA,GACF,CAAA;AAEA,EAAO,OAAA;AAAA,IACL,OAAA,EAAS,cAAe,CAAA,sBAAA,CAAuB,SAAS,CAAA;AAAA,IACxD,OAAA,EAAS,cAAe,CAAA,sBAAA,CAAuB,SAAS,CAAA;AAAA,GAC1D,CAAA;AACF,CAAA;AAKO,SAAS,qBAAqB,MAAmC,EAAA;AACtE,EAAM,MAAA,UAAA,GACJ,OACA,2BAA2B,CAAA,CAAA;AAE7B,EAAM,MAAA,SAAA,GAAY,2BAA2B,MAAM,CAAA,CAAA;AACnD,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AAEA,EAAA,OACE,YAAY,OACT,CAAA,MAAA,CAAO,CAAC,EAAE,MAAW,KAAA;AACpB,IAAA,IAAI,SAAU,CAAA,OAAA,EAAS,QAAS,CAAA,IAAI,CAAG,EAAA;AACrC,MAAO,OAAA,KAAA,CAAA;AAAA,KACT;AACA,IAAA,IAAI,UAAU,OAAW,IAAA,CAAC,UAAU,OAAQ,CAAA,QAAA,CAAS,IAAI,CAAG,EAAA;AAC1D,MAAO,OAAA,KAAA,CAAA;AAAA,KACT;AACA,IAAO,OAAA,IAAA,CAAA;AAAA,GACR,CACA,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,CAClB,CAAA,MAAA,CAAO,kBAAkB,CAAA,IAAK,EAAC,CAAA;AAEtC,CAAA;AAEA,SAAS,mBAAmB,GAAsC,EAAA;AAChE,EAAA,IAAI,QAAQ,IAAQ,IAAA,OAAO,GAAQ,KAAA,QAAA,IAAY,YAAY,GAAK,EAAA;AAC9D,IAAA,OACE,GAAI,CAAA,MAAA,KAAW,2BACf,IAAA,GAAA,CAAI,MAAW,KAAA,2BAAA;AAAA;AAAA,IAGf,GAAI,CAAA,MAAA,KAAW,4BACf,IAAA,GAAA,CAAI,MAAW,KAAA,+BAAA,CAAA;AAAA,GAEnB;AACA,EAAO,OAAA,KAAA,CAAA;AACT;;;;"}
@@ -0,0 +1,78 @@
1
+ import React, { ReactNode, JSX } from 'react';
2
+ import { ConfigApi } from '@backstage/frontend-plugin-api';
3
+ import { FrontendFeature, CreateAppRouteBinder } from '@backstage/frontend-app-api';
4
+
5
+ /**
6
+ * A source of dynamically loaded frontend features.
7
+ *
8
+ * @public
9
+ */
10
+ interface CreateAppFeatureLoader {
11
+ /**
12
+ * Returns name of this loader. suitable for showing to users.
13
+ */
14
+ getLoaderName(): string;
15
+ /**
16
+ * Loads a number of features dynamically.
17
+ */
18
+ load(options: {
19
+ config: ConfigApi;
20
+ }): Promise<{
21
+ features: FrontendFeature[];
22
+ }>;
23
+ }
24
+ /**
25
+ * Options for {@link createApp}.
26
+ *
27
+ * @public
28
+ */
29
+ interface CreateAppOptions {
30
+ features?: (FrontendFeature | CreateAppFeatureLoader)[];
31
+ configLoader?: () => Promise<{
32
+ config: ConfigApi;
33
+ }>;
34
+ bindRoutes?(context: {
35
+ bind: CreateAppRouteBinder;
36
+ }): void;
37
+ /**
38
+ * The component to render while loading the app (waiting for config, features, etc)
39
+ *
40
+ * Is the text "Loading..." by default.
41
+ * If set to "null" then no loading fallback component is rendered. *
42
+ */
43
+ loadingComponent?: ReactNode;
44
+ }
45
+ /**
46
+ * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index
47
+ *
48
+ * @public
49
+ */
50
+ declare function createApp(options?: CreateAppOptions): {
51
+ createRoot(): JSX.Element;
52
+ };
53
+
54
+ /**
55
+ * Creates an app that is suitable for the public sign-in page, for use in the `index-public-experimental.tsx` file.
56
+ *
57
+ * @remarks
58
+ *
59
+ * This app has an override for the `app/layout` extension, which means that
60
+ * most extension typically installed in an app will be ignored. However, you
61
+ * can still for example install API and root element extensions.
62
+ *
63
+ * A typical setup of this app will only install a custom sign-in page.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const app = createPublicSignInApp({
68
+ * features: [signInPageModule],
69
+ * });
70
+ * ```
71
+ *
72
+ * @public
73
+ */
74
+ declare function createPublicSignInApp(options?: CreateAppOptions): {
75
+ createRoot(): React.JSX.Element;
76
+ };
77
+
78
+ export { type CreateAppFeatureLoader, type CreateAppOptions, createApp, createPublicSignInApp };
@@ -0,0 +1,3 @@
1
+ export { createApp } from './createApp.esm.js';
2
+ export { createPublicSignInApp } from './createPublicSignInApp.esm.js';
3
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@backstage/frontend-defaults",
3
+ "version": "0.0.0-nightly-20240830022837",
4
+ "main": "dist/index.esm.js",
5
+ "types": "dist/index.d.ts",
6
+ "license": "Apache-2.0",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "main": "dist/index.esm.js",
10
+ "types": "dist/index.d.ts"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/backstage/backstage",
15
+ "directory": "packages/frontend-defaults"
16
+ },
17
+ "backstage": {
18
+ "role": "web-library"
19
+ },
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "start": "backstage-cli package start",
23
+ "build": "backstage-cli package build",
24
+ "lint": "backstage-cli package lint",
25
+ "test": "backstage-cli package test",
26
+ "clean": "backstage-cli package clean",
27
+ "prepack": "backstage-cli package prepack",
28
+ "postpack": "backstage-cli package postpack"
29
+ },
30
+ "devDependencies": {
31
+ "@backstage/cli": "^0.0.0-nightly-20240830022837",
32
+ "@backstage/core-plugin-api": "^1.9.3",
33
+ "@backstage/test-utils": "^0.0.0-nightly-20240830022837",
34
+ "@testing-library/jest-dom": "^6.0.0",
35
+ "@testing-library/react": "^15.0.0"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "dependencies": {
41
+ "@backstage/config": "^1.2.0",
42
+ "@backstage/errors": "^1.2.4",
43
+ "@backstage/frontend-app-api": "^0.0.0-nightly-20240830022837",
44
+ "@backstage/frontend-plugin-api": "^0.0.0-nightly-20240830022837",
45
+ "@backstage/plugin-app": "^0.0.0-nightly-20240830022837",
46
+ "@react-hookz/web": "^24.0.0",
47
+ "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0"
48
+ },
49
+ "peerDependencies": {
50
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
51
+ },
52
+ "module": "./dist/index.esm.js"
53
+ }