@backstage/plugin-app-backend 0.3.75 → 0.3.76

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +40 -2
  2. package/README.md +1 -1
  3. package/alpha/package.json +1 -1
  4. package/dist/alpha.cjs.js +3 -72
  5. package/dist/alpha.cjs.js.map +1 -1
  6. package/dist/alpha.d.ts +3 -6
  7. package/dist/index.cjs.js +5 -15
  8. package/dist/index.cjs.js.map +1 -1
  9. package/dist/index.d.ts +8 -1
  10. package/dist/lib/assets/StaticAssetsStore.cjs.js +112 -0
  11. package/dist/lib/assets/StaticAssetsStore.cjs.js.map +1 -0
  12. package/dist/lib/assets/createStaticAssetMiddleware.cjs.js +35 -0
  13. package/dist/lib/assets/createStaticAssetMiddleware.cjs.js.map +1 -0
  14. package/dist/lib/assets/findStaticAssets.cjs.js +26 -0
  15. package/dist/lib/assets/findStaticAssets.cjs.js.map +1 -0
  16. package/dist/lib/config/injectConfig.cjs.js +12 -0
  17. package/dist/lib/config/injectConfig.cjs.js.map +1 -0
  18. package/dist/lib/config/injectConfigIntoHtml.cjs.js +58 -0
  19. package/dist/lib/config/injectConfigIntoHtml.cjs.js.map +1 -0
  20. package/dist/lib/config/injectConfigIntoStatic.cjs.js +42 -0
  21. package/dist/lib/config/injectConfigIntoStatic.cjs.js.map +1 -0
  22. package/dist/lib/config/readFrontendConfig.cjs.js +35 -0
  23. package/dist/lib/config/readFrontendConfig.cjs.js.map +1 -0
  24. package/dist/lib/headers.cjs.js +10 -0
  25. package/dist/lib/headers.cjs.js.map +1 -0
  26. package/dist/service/appPlugin.cjs.js +64 -0
  27. package/dist/service/appPlugin.cjs.js.map +1 -0
  28. package/dist/service/router.cjs.js +185 -0
  29. package/dist/service/router.cjs.js.map +1 -0
  30. package/package.json +10 -8
  31. package/dist/cjs/router-Bi0af47X.cjs.js +0 -435
  32. package/dist/cjs/router-Bi0af47X.cjs.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,10 +1,48 @@
1
1
  # @backstage/plugin-app-backend
2
2
 
3
- ## 0.3.75
3
+ ## 0.3.76
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - 9206a12: Fixed unexpected behaviour where configuration supplied with `APP_CONFIG_*` environment variables where not filtered by the configuration schema.
7
+ - 2c4ee26: Fixed unexpected behaviour where configuration supplied with `APP_CONFIG_*` environment variables where not filtered by the configuration schema.
8
+ - 094eaa3: Remove references to in-repo backend-common
9
+ - 3109c24: The export for the new backend system at the `/alpha` export is now also available via the main entry point, which means that you can remove the `/alpha` suffix from the import.
10
+ - Updated dependencies
11
+ - @backstage/plugin-auth-node@0.5.3
12
+ - @backstage/backend-plugin-api@1.0.1
13
+ - @backstage/config@1.2.0
14
+ - @backstage/config-loader@1.9.1
15
+ - @backstage/errors@1.2.4
16
+ - @backstage/types@1.1.1
17
+ - @backstage/plugin-app-node@0.1.26
18
+
19
+ ## 0.3.76-next.1
20
+
21
+ ### Patch Changes
22
+
23
+ - 2c4ee26: Fixed unexpected behaviour where configuration supplied with `APP_CONFIG_*` environment variables where not filtered by the configuration schema.
24
+ - Updated dependencies
25
+ - @backstage/plugin-auth-node@0.5.3-next.1
26
+ - @backstage/backend-plugin-api@1.0.1-next.1
27
+ - @backstage/config@1.2.0
28
+ - @backstage/config-loader@1.9.1
29
+ - @backstage/errors@1.2.4
30
+ - @backstage/types@1.1.1
31
+ - @backstage/plugin-app-node@0.1.26-next.1
32
+
33
+ ## 0.3.75-next.0
34
+
35
+ ### Patch Changes
36
+
37
+ - 094eaa3: Remove references to in-repo backend-common
38
+ - Updated dependencies
39
+ - @backstage/plugin-auth-node@0.5.3-next.0
40
+ - @backstage/backend-plugin-api@1.0.1-next.0
41
+ - @backstage/config@1.2.0
42
+ - @backstage/config-loader@1.9.1
43
+ - @backstage/errors@1.2.4
44
+ - @backstage/types@1.1.1
45
+ - @backstage/plugin-app-node@0.1.26-next.0
8
46
 
9
47
  ## 0.3.74
10
48
 
package/README.md CHANGED
@@ -21,7 +21,7 @@ Now add the plugin to your app, creating it for example like this:
21
21
  import { createBackend } from '@backstage/backend-defaults';
22
22
 
23
23
  const backend = createBackend();
24
- backend.add(import('@backstage/plugin-app-backend/alpha'));
24
+ backend.add(import('@backstage/plugin-app-backend'));
25
25
  backend.start();
26
26
  ```
27
27
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-app-backend__alpha",
3
- "version": "0.3.75",
3
+ "version": "0.3.76",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -2,78 +2,9 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var backendPluginApi = require('@backstage/backend-plugin-api');
6
- var router = require('./cjs/router-Bi0af47X.cjs.js');
7
- var pluginAppNode = require('@backstage/plugin-app-node');
8
- require('@backstage/backend-common');
9
- require('helmet');
10
- require('express');
11
- require('express-promise-router');
12
- require('fs-extra');
13
- require('path');
14
- require('luxon');
15
- require('lodash/partition');
16
- require('globby');
17
- require('@backstage/errors');
18
- require('lodash/template');
19
- require('@backstage/config');
20
- require('@backstage/config-loader');
5
+ var appPlugin = require('./service/appPlugin.cjs.js');
21
6
 
22
- const appPlugin = backendPluginApi.createBackendPlugin({
23
- pluginId: "app",
24
- register(env) {
25
- let staticFallbackHandler;
26
- let schema;
27
- env.registerExtensionPoint(pluginAppNode.staticFallbackHandlerExtensionPoint, {
28
- setStaticFallbackHandler(handler) {
29
- if (staticFallbackHandler) {
30
- throw new Error(
31
- "Attempted to install a static fallback handler for the app-backend twice"
32
- );
33
- }
34
- staticFallbackHandler = handler;
35
- }
36
- });
37
- env.registerExtensionPoint(pluginAppNode.configSchemaExtensionPoint, {
38
- setConfigSchema(configSchema) {
39
- if (schema) {
40
- throw new Error(
41
- "Attempted to set config schema for the app-backend twice"
42
- );
43
- }
44
- schema = configSchema;
45
- }
46
- });
47
- env.registerInit({
48
- deps: {
49
- logger: backendPluginApi.coreServices.logger,
50
- config: backendPluginApi.coreServices.rootConfig,
51
- database: backendPluginApi.coreServices.database,
52
- httpRouter: backendPluginApi.coreServices.httpRouter,
53
- auth: backendPluginApi.coreServices.auth,
54
- httpAuth: backendPluginApi.coreServices.httpAuth
55
- },
56
- async init({ logger, config, database, httpRouter, auth, httpAuth }) {
57
- const appPackageName = config.getOptionalString("app.packageName") ?? "app";
58
- const router$1 = await router.createRouter({
59
- logger,
60
- config,
61
- database,
62
- auth,
63
- httpAuth,
64
- appPackageName,
65
- staticFallbackHandler,
66
- schema
67
- });
68
- httpRouter.use(router$1);
69
- httpRouter.addAuthPolicy({
70
- allow: "unauthenticated",
71
- path: "/"
72
- });
73
- }
74
- });
75
- }
76
- });
7
+ const _appPlugin = appPlugin.appPlugin;
77
8
 
78
- exports.default = appPlugin;
9
+ exports.default = _appPlugin;
79
10
  //# sourceMappingURL=alpha.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.cjs.js","sources":["../src/service/appPlugin.ts"],"sourcesContent":["/*\n * Copyright 2022 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 express from 'express';\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\nimport {\n configSchemaExtensionPoint,\n staticFallbackHandlerExtensionPoint,\n} from '@backstage/plugin-app-node';\nimport { ConfigSchema } from '@backstage/config-loader';\n\n/**\n * The App plugin is responsible for serving the frontend app bundle and static assets.\n * @alpha\n */\nexport const appPlugin = createBackendPlugin({\n pluginId: 'app',\n register(env) {\n let staticFallbackHandler: express.Handler | undefined;\n let schema: ConfigSchema | undefined;\n\n env.registerExtensionPoint(staticFallbackHandlerExtensionPoint, {\n setStaticFallbackHandler(handler) {\n if (staticFallbackHandler) {\n throw new Error(\n 'Attempted to install a static fallback handler for the app-backend twice',\n );\n }\n staticFallbackHandler = handler;\n },\n });\n\n env.registerExtensionPoint(configSchemaExtensionPoint, {\n setConfigSchema(configSchema) {\n if (schema) {\n throw new Error(\n 'Attempted to set config schema for the app-backend twice',\n );\n }\n schema = configSchema;\n },\n });\n\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n },\n async init({ logger, config, database, httpRouter, auth, httpAuth }) {\n const appPackageName =\n config.getOptionalString('app.packageName') ?? 'app';\n\n const router = await createRouter({\n logger,\n config,\n database,\n auth,\n httpAuth,\n appPackageName,\n staticFallbackHandler,\n schema,\n });\n httpRouter.use(router);\n\n // Access control is handled within the router\n httpRouter.addAuthPolicy({\n allow: 'unauthenticated',\n path: '/',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","staticFallbackHandlerExtensionPoint","configSchemaExtensionPoint","coreServices","router","createRouter"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCO,MAAM,YAAYA,oCAAoB,CAAA;AAAA,EAC3C,QAAU,EAAA,KAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAI,IAAA,qBAAA,CAAA;AACJ,IAAI,IAAA,MAAA,CAAA;AAEJ,IAAA,GAAA,CAAI,uBAAuBC,iDAAqC,EAAA;AAAA,MAC9D,yBAAyB,OAAS,EAAA;AAChC,QAAA,IAAI,qBAAuB,EAAA;AACzB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,0EAAA;AAAA,WACF,CAAA;AAAA,SACF;AACA,QAAwB,qBAAA,GAAA,OAAA,CAAA;AAAA,OAC1B;AAAA,KACD,CAAA,CAAA;AAED,IAAA,GAAA,CAAI,uBAAuBC,wCAA4B,EAAA;AAAA,MACrD,gBAAgB,YAAc,EAAA;AAC5B,QAAA,IAAI,MAAQ,EAAA;AACV,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,0DAAA;AAAA,WACF,CAAA;AAAA,SACF;AACA,QAAS,MAAA,GAAA,YAAA,CAAA;AAAA,OACX;AAAA,KACD,CAAA,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,OACzB;AAAA,MACA,MAAM,KAAK,EAAE,MAAA,EAAQ,QAAQ,QAAU,EAAA,UAAA,EAAY,IAAM,EAAA,QAAA,EAAY,EAAA;AACnE,QAAA,MAAM,cACJ,GAAA,MAAA,CAAO,iBAAkB,CAAA,iBAAiB,CAAK,IAAA,KAAA,CAAA;AAEjD,QAAM,MAAAC,QAAA,GAAS,MAAMC,mBAAa,CAAA;AAAA,UAChC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,QAAA;AAAA,UACA,IAAA;AAAA,UACA,QAAA;AAAA,UACA,cAAA;AAAA,UACA,qBAAA;AAAA,UACA,MAAA;AAAA,SACD,CAAA,CAAA;AACD,QAAA,UAAA,CAAW,IAAID,QAAM,CAAA,CAAA;AAGrB,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,KAAO,EAAA,iBAAA;AAAA,UACP,IAAM,EAAA,GAAA;AAAA,SACP,CAAA,CAAA;AAAA,OACH;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.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 { appPlugin } from './service/appPlugin';\n\n/** @alpha */\nconst _appPlugin = appPlugin;\nexport default _appPlugin;\n"],"names":["appPlugin"],"mappings":";;;;;;AAmBA,MAAM,UAAa,GAAAA;;;;"}
package/dist/alpha.d.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
2
 
3
- /**
4
- * The App plugin is responsible for serving the frontend app bundle and static assets.
5
- * @alpha
6
- */
7
- declare const appPlugin: _backstage_backend_plugin_api.BackendFeature;
3
+ /** @alpha */
4
+ declare const _appPlugin: _backstage_backend_plugin_api.BackendFeature;
8
5
 
9
- export { appPlugin as default };
6
+ export { _appPlugin as default };
package/dist/index.cjs.js CHANGED
@@ -1,22 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var router = require('./cjs/router-Bi0af47X.cjs.js');
4
- require('@backstage/backend-common');
5
- require('@backstage/backend-plugin-api');
6
- require('helmet');
7
- require('express');
8
- require('express-promise-router');
9
- require('fs-extra');
10
- require('path');
11
- require('luxon');
12
- require('lodash/partition');
13
- require('globby');
14
- require('@backstage/errors');
15
- require('lodash/template');
16
- require('@backstage/config');
17
- require('@backstage/config-loader');
3
+ Object.defineProperty(exports, '__esModule', { value: true });
18
4
 
5
+ var appPlugin = require('./service/appPlugin.cjs.js');
6
+ var router = require('./service/router.cjs.js');
19
7
 
20
8
 
9
+
10
+ exports.default = appPlugin.appPlugin;
21
11
  exports.createRouter = router.createRouter;
22
12
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,14 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
1
2
  import { RootConfigService, LoggerService, AuthService, HttpAuthService, DatabaseService } from '@backstage/backend-plugin-api';
2
3
  import express from 'express';
3
4
  import { ConfigSchema } from '@backstage/config-loader';
4
5
 
6
+ /**
7
+ * The App plugin is responsible for serving the frontend app bundle and static assets.
8
+ * @public
9
+ */
10
+ declare const appPlugin: _backstage_backend_plugin_api.BackendFeature;
11
+
5
12
  /**
6
13
  * @public
7
14
  * @deprecated Please migrate to the new backend system as this will be removed in the future.
@@ -60,4 +67,4 @@ interface RouterOptions {
60
67
  */
61
68
  declare function createRouter(options: RouterOptions): Promise<express.Router>;
62
69
 
63
- export { type RouterOptions, createRouter };
70
+ export { type RouterOptions, createRouter, appPlugin as default };
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ var luxon = require('luxon');
4
+ var partition = require('lodash/partition');
5
+ var backendPluginApi = require('@backstage/backend-plugin-api');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var partition__default = /*#__PURE__*/_interopDefaultCompat(partition);
10
+
11
+ const migrationsDir = backendPluginApi.resolvePackagePath(
12
+ "@backstage/plugin-app-backend",
13
+ "migrations"
14
+ );
15
+ class StaticAssetsStore {
16
+ #db;
17
+ #logger;
18
+ #namespace;
19
+ static async create(options) {
20
+ const { database } = options;
21
+ const client = await database.getClient();
22
+ if (!database.migrations?.skip) {
23
+ await client.migrate.latest({
24
+ directory: migrationsDir
25
+ });
26
+ }
27
+ return new StaticAssetsStore(client, options.logger);
28
+ }
29
+ constructor(client, logger, namespace) {
30
+ this.#db = client;
31
+ this.#logger = logger;
32
+ this.#namespace = namespace ?? "default";
33
+ }
34
+ /**
35
+ * Creates a new store with the provided namespace, using the same underlying storage.
36
+ */
37
+ withNamespace(namespace) {
38
+ return new StaticAssetsStore(this.#db, this.#logger, namespace);
39
+ }
40
+ /**
41
+ * Store the provided assets.
42
+ *
43
+ * If an asset for a given path already exists the modification time will be
44
+ * updated, but the contents will not.
45
+ */
46
+ async storeAssets(assets) {
47
+ const existingRows = await this.#db("static_assets_cache").where("namespace", this.#namespace).whereIn(
48
+ "path",
49
+ assets.map((a) => a.path)
50
+ );
51
+ const existingAssetPaths = new Set(existingRows.map((r) => r.path));
52
+ const [modified, added] = partition__default.default(
53
+ assets,
54
+ (asset) => existingAssetPaths.has(asset.path)
55
+ );
56
+ this.#logger.info(
57
+ `Storing ${modified.length} updated assets and ${added.length} new assets`
58
+ );
59
+ await this.#db("static_assets_cache").update({
60
+ last_modified_at: this.#db.fn.now()
61
+ }).where("namespace", this.#namespace).whereIn(
62
+ "path",
63
+ modified.map((a) => a.path)
64
+ );
65
+ for (const asset of added) {
66
+ await this.#db("static_assets_cache").insert({
67
+ path: asset.path,
68
+ content: await asset.content(),
69
+ namespace: this.#namespace
70
+ }).onConflict(["namespace", "path"]).ignore();
71
+ }
72
+ }
73
+ /**
74
+ * Retrieve an asset from the store with the given path.
75
+ */
76
+ async getAsset(path) {
77
+ const [row] = await this.#db("static_assets_cache").where({
78
+ path,
79
+ namespace: this.#namespace
80
+ });
81
+ if (!row) {
82
+ return void 0;
83
+ }
84
+ return {
85
+ path: row.path,
86
+ content: row.content,
87
+ lastModifiedAt: typeof row.last_modified_at === "string" ? luxon.DateTime.fromSQL(row.last_modified_at, { zone: "UTC" }).toJSDate() : row.last_modified_at
88
+ };
89
+ }
90
+ /**
91
+ * Delete any assets from the store whose modification time is older than the max age.
92
+ */
93
+ async trimAssets(options) {
94
+ const { maxAgeSeconds } = options;
95
+ let lastModifiedInterval = this.#db.raw(
96
+ `now() + interval '${-maxAgeSeconds} seconds'`
97
+ );
98
+ if (this.#db.client.config.client.includes("mysql")) {
99
+ lastModifiedInterval = this.#db.raw(
100
+ `date_sub(now(), interval ${maxAgeSeconds} second)`
101
+ );
102
+ } else if (this.#db.client.config.client.includes("sqlite3")) {
103
+ lastModifiedInterval = this.#db.raw(`datetime('now', ?)`, [
104
+ `-${maxAgeSeconds} seconds`
105
+ ]);
106
+ }
107
+ await this.#db("static_assets_cache").where("namespace", this.#namespace).where("last_modified_at", "<=", lastModifiedInterval).delete();
108
+ }
109
+ }
110
+
111
+ exports.StaticAssetsStore = StaticAssetsStore;
112
+ //# sourceMappingURL=StaticAssetsStore.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaticAssetsStore.cjs.js","sources":["../../../src/lib/assets/StaticAssetsStore.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { PluginDatabaseManager } from '@backstage/backend-common';\nimport { Knex } from 'knex';\nimport { DateTime } from 'luxon';\nimport partition from 'lodash/partition';\nimport { StaticAsset, StaticAssetInput, StaticAssetProvider } from './types';\nimport {\n LoggerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-app-backend',\n 'migrations',\n);\n\ninterface StaticAssetRow {\n path: string;\n content: Buffer;\n namespace: string | null;\n last_modified_at: Date;\n}\n\n/** @internal */\nexport interface StaticAssetsStoreOptions {\n database: PluginDatabaseManager;\n logger: LoggerService;\n}\n\n/**\n * A storage for static assets that are assumed to be immutable.\n *\n * @internal\n */\nexport class StaticAssetsStore implements StaticAssetProvider {\n #db: Knex;\n #logger: LoggerService;\n #namespace: string;\n\n static async create(options: StaticAssetsStoreOptions) {\n const { database } = options;\n const client = await database.getClient();\n\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new StaticAssetsStore(client, options.logger);\n }\n\n private constructor(client: Knex, logger: LoggerService, namespace?: string) {\n this.#db = client;\n this.#logger = logger;\n this.#namespace = namespace ?? 'default';\n }\n\n /**\n * Creates a new store with the provided namespace, using the same underlying storage.\n */\n withNamespace(namespace: string): StaticAssetsStore {\n return new StaticAssetsStore(this.#db, this.#logger, namespace);\n }\n\n /**\n * Store the provided assets.\n *\n * If an asset for a given path already exists the modification time will be\n * updated, but the contents will not.\n */\n async storeAssets(assets: StaticAssetInput[]) {\n const existingRows = await this.#db<StaticAssetRow>('static_assets_cache')\n .where('namespace', this.#namespace)\n .whereIn(\n 'path',\n assets.map(a => a.path),\n );\n const existingAssetPaths = new Set(existingRows.map(r => r.path));\n\n const [modified, added] = partition(assets, asset =>\n existingAssetPaths.has(asset.path),\n );\n\n this.#logger.info(\n `Storing ${modified.length} updated assets and ${added.length} new assets`,\n );\n\n await this.#db('static_assets_cache')\n .update({\n last_modified_at: this.#db.fn.now(),\n })\n .where('namespace', this.#namespace)\n .whereIn(\n 'path',\n modified.map(a => a.path),\n );\n\n for (const asset of added) {\n // We ignore conflicts with other nodes, it doesn't matter if someone else\n // added the same asset just before us.\n await this.#db('static_assets_cache')\n .insert({\n path: asset.path,\n content: await asset.content(),\n namespace: this.#namespace,\n })\n .onConflict(['namespace', 'path'])\n .ignore();\n }\n }\n\n /**\n * Retrieve an asset from the store with the given path.\n */\n async getAsset(path: string): Promise<StaticAsset | undefined> {\n const [row] = await this.#db<StaticAssetRow>('static_assets_cache').where({\n path,\n namespace: this.#namespace,\n });\n if (!row) {\n return undefined;\n }\n return {\n path: row.path,\n content: row.content,\n lastModifiedAt:\n typeof row.last_modified_at === 'string'\n ? DateTime.fromSQL(row.last_modified_at, { zone: 'UTC' }).toJSDate()\n : row.last_modified_at,\n };\n }\n\n /**\n * Delete any assets from the store whose modification time is older than the max age.\n */\n async trimAssets(options: { maxAgeSeconds: number }) {\n const { maxAgeSeconds } = options;\n let lastModifiedInterval = this.#db.raw(\n `now() + interval '${-maxAgeSeconds} seconds'`,\n );\n if (this.#db.client.config.client.includes('mysql')) {\n lastModifiedInterval = this.#db.raw(\n `date_sub(now(), interval ${maxAgeSeconds} second)`,\n );\n } else if (this.#db.client.config.client.includes('sqlite3')) {\n lastModifiedInterval = this.#db.raw(`datetime('now', ?)`, [\n `-${maxAgeSeconds} seconds`,\n ]);\n }\n await this.#db<StaticAssetRow>('static_assets_cache')\n .where('namespace', this.#namespace)\n .where('last_modified_at', '<=', lastModifiedInterval)\n .delete();\n }\n}\n"],"names":["resolvePackagePath","partition","DateTime"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ var path = require('path');
4
+ var headers = require('../headers.cjs.js');
5
+
6
+ function createStaticAssetMiddleware(store) {
7
+ return (req, res, next) => {
8
+ if (req.method !== "GET" && req.method !== "HEAD") {
9
+ next();
10
+ return;
11
+ }
12
+ Promise.resolve(
13
+ (async () => {
14
+ const path$1 = req.path.startsWith("/") ? req.path.slice(1) : req.path;
15
+ const asset = await store.getAsset(path$1);
16
+ if (!asset) {
17
+ next();
18
+ return;
19
+ }
20
+ const ext = path.extname(asset.path);
21
+ if (ext) {
22
+ res.type(ext);
23
+ } else {
24
+ res.type("bin");
25
+ }
26
+ res.setHeader("Cache-Control", headers.CACHE_CONTROL_MAX_CACHE);
27
+ res.setHeader("Last-Modified", asset.lastModifiedAt.toUTCString());
28
+ res.send(asset.content);
29
+ })()
30
+ ).catch(next);
31
+ };
32
+ }
33
+
34
+ exports.createStaticAssetMiddleware = createStaticAssetMiddleware;
35
+ //# sourceMappingURL=createStaticAssetMiddleware.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createStaticAssetMiddleware.cjs.js","sources":["../../../src/lib/assets/createStaticAssetMiddleware.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { extname } from 'path';\nimport { RequestHandler } from 'express';\nimport { StaticAssetProvider } from './types';\nimport { CACHE_CONTROL_MAX_CACHE } from '../headers';\n\n/**\n * Creates a middleware that serves static assets from a static asset provider\n *\n * @internal\n */\nexport function createStaticAssetMiddleware(\n store: StaticAssetProvider,\n): RequestHandler {\n return (req, res, next) => {\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n next();\n return;\n }\n\n // Let's not assume we're in promise-router\n Promise.resolve(\n (async () => {\n // Drop leading slashes from the incoming path\n const path = req.path.startsWith('/') ? req.path.slice(1) : req.path;\n\n const asset = await store.getAsset(path);\n if (!asset) {\n next();\n return;\n }\n\n // Set the Content-Type header, falling back to octet-stream\n const ext = extname(asset.path);\n if (ext) {\n res.type(ext);\n } else {\n res.type('bin');\n }\n\n // Same as our express.static override\n res.setHeader('Cache-Control', CACHE_CONTROL_MAX_CACHE);\n res.setHeader('Last-Modified', asset.lastModifiedAt.toUTCString());\n\n res.send(asset.content);\n })(),\n ).catch(next);\n };\n}\n"],"names":["path","extname","CACHE_CONTROL_MAX_CACHE"],"mappings":";;;;;AA0BO,SAAS,4BACd,KACgB,EAAA;AAChB,EAAO,OAAA,CAAC,GAAK,EAAA,GAAA,EAAK,IAAS,KAAA;AACzB,IAAA,IAAI,GAAI,CAAA,MAAA,KAAW,KAAS,IAAA,GAAA,CAAI,WAAW,MAAQ,EAAA;AACjD,MAAK,IAAA,EAAA,CAAA;AACL,MAAA,OAAA;AAAA,KACF;AAGA,IAAQ,OAAA,CAAA,OAAA;AAAA,MAAA,CACL,YAAY;AAEX,QAAM,MAAAA,MAAA,GAAO,GAAI,CAAA,IAAA,CAAK,UAAW,CAAA,GAAG,CAAI,GAAA,GAAA,CAAI,IAAK,CAAA,KAAA,CAAM,CAAC,CAAA,GAAI,GAAI,CAAA,IAAA,CAAA;AAEhE,QAAA,MAAM,KAAQ,GAAA,MAAM,KAAM,CAAA,QAAA,CAASA,MAAI,CAAA,CAAA;AACvC,QAAA,IAAI,CAAC,KAAO,EAAA;AACV,UAAK,IAAA,EAAA,CAAA;AACL,UAAA,OAAA;AAAA,SACF;AAGA,QAAM,MAAA,GAAA,GAAMC,YAAQ,CAAA,KAAA,CAAM,IAAI,CAAA,CAAA;AAC9B,QAAA,IAAI,GAAK,EAAA;AACP,UAAA,GAAA,CAAI,KAAK,GAAG,CAAA,CAAA;AAAA,SACP,MAAA;AACL,UAAA,GAAA,CAAI,KAAK,KAAK,CAAA,CAAA;AAAA,SAChB;AAGA,QAAI,GAAA,CAAA,SAAA,CAAU,iBAAiBC,+BAAuB,CAAA,CAAA;AACtD,QAAA,GAAA,CAAI,SAAU,CAAA,eAAA,EAAiB,KAAM,CAAA,cAAA,CAAe,aAAa,CAAA,CAAA;AAEjE,QAAI,GAAA,CAAA,IAAA,CAAK,MAAM,OAAO,CAAA,CAAA;AAAA,OACrB,GAAA;AAAA,KACL,CAAE,MAAM,IAAI,CAAA,CAAA;AAAA,GACd,CAAA;AACF;;;;"}
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs-extra');
4
+ var globby = require('globby');
5
+ var backendPluginApi = require('@backstage/backend-plugin-api');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
10
+ var globby__default = /*#__PURE__*/_interopDefaultCompat(globby);
11
+
12
+ async function findStaticAssets(staticDir) {
13
+ const assetPaths = await globby__default.default("**/*", {
14
+ ignore: ["**/*.map"],
15
+ // Ignore source maps since they're quite large
16
+ cwd: staticDir,
17
+ dot: true
18
+ });
19
+ return assetPaths.map((path) => ({
20
+ path,
21
+ content: async () => fs__default.default.readFile(backendPluginApi.resolveSafeChildPath(staticDir, path))
22
+ }));
23
+ }
24
+
25
+ exports.findStaticAssets = findStaticAssets;
26
+ //# sourceMappingURL=findStaticAssets.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findStaticAssets.cjs.js","sources":["../../../src/lib/assets/findStaticAssets.ts"],"sourcesContent":["/*\n * Copyright 2021 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 fs from 'fs-extra';\nimport globby from 'globby';\nimport { StaticAssetInput } from './types';\nimport { resolveSafeChildPath } from '@backstage/backend-plugin-api';\n\n/**\n * Finds all static assets within a directory\n *\n * @internal\n */\nexport async function findStaticAssets(\n staticDir: string,\n): Promise<StaticAssetInput[]> {\n const assetPaths = await globby('**/*', {\n ignore: ['**/*.map'], // Ignore source maps since they're quite large\n cwd: staticDir,\n dot: true,\n });\n\n return assetPaths.map(path => ({\n path,\n content: async () => fs.readFile(resolveSafeChildPath(staticDir, path)),\n }));\n}\n"],"names":["globby","fs","resolveSafeChildPath"],"mappings":";;;;;;;;;;;AA0BA,eAAsB,iBACpB,SAC6B,EAAA;AAC7B,EAAM,MAAA,UAAA,GAAa,MAAMA,uBAAA,CAAO,MAAQ,EAAA;AAAA,IACtC,MAAA,EAAQ,CAAC,UAAU,CAAA;AAAA;AAAA,IACnB,GAAK,EAAA,SAAA;AAAA,IACL,GAAK,EAAA,IAAA;AAAA,GACN,CAAA,CAAA;AAED,EAAO,OAAA,UAAA,CAAW,IAAI,CAAS,IAAA,MAAA;AAAA,IAC7B,IAAA;AAAA,IACA,SAAS,YAAYC,mBAAA,CAAG,SAASC,qCAAqB,CAAA,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,GACtE,CAAA,CAAA,CAAA;AACJ;;;;"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var injectConfigIntoHtml = require('./injectConfigIntoHtml.cjs.js');
4
+ var injectConfigIntoStatic = require('./injectConfigIntoStatic.cjs.js');
5
+
6
+ async function injectConfig(options) {
7
+ await injectConfigIntoHtml.injectConfigIntoHtml(options);
8
+ return injectConfigIntoStatic.injectConfigIntoStatic(options);
9
+ }
10
+
11
+ exports.injectConfig = injectConfig;
12
+ //# sourceMappingURL=injectConfig.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectConfig.cjs.js","sources":["../../../src/lib/config/injectConfig.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 { injectConfigIntoHtml } from './injectConfigIntoHtml';\nimport { injectConfigIntoStatic } from './injectConfigIntoStatic';\nimport { InjectOptions } from './types';\n\n/**\n * Injects configs into the app bundle, replacing any existing injected config.\n * @internal\n */\nexport async function injectConfig(\n options: InjectOptions,\n): Promise<string | undefined> {\n // In order to minimize the potential impact when rolling out the new config\n // injection, we use both methods for a few releases. This allows the frontend\n // app to be behind the backend by a version or two, but temporarily increases\n // config injection overhead.\n // TODO(Rugvip): After the 1.32 release we can stop calling the static injection if the HTML one is successful\n await injectConfigIntoHtml(options);\n\n return injectConfigIntoStatic(options);\n}\n"],"names":["injectConfigIntoHtml","injectConfigIntoStatic"],"mappings":";;;;;AAwBA,eAAsB,aACpB,OAC6B,EAAA;AAM7B,EAAA,MAAMA,0CAAqB,OAAO,CAAA,CAAA;AAElC,EAAA,OAAOC,8CAAuB,OAAO,CAAA,CAAA;AACvC;;;;"}
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs-extra');
4
+ var path = require('path');
5
+ var compileTemplate = require('lodash/template');
6
+ var config = require('@backstage/config');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
11
+ var compileTemplate__default = /*#__PURE__*/_interopDefaultCompat(compileTemplate);
12
+
13
+ const HTML_TEMPLATE_NAME = "index.html.tmpl";
14
+ async function injectConfigIntoHtml(options) {
15
+ const { rootDir, appConfigs } = options;
16
+ const templatePath = path.resolve(rootDir, HTML_TEMPLATE_NAME);
17
+ if (!await fs__default.default.exists(templatePath)) {
18
+ return false;
19
+ }
20
+ const templateContent = await fs__default.default.readFile(
21
+ path.resolve(rootDir, HTML_TEMPLATE_NAME),
22
+ "utf8"
23
+ );
24
+ const config$1 = config.ConfigReader.fromConfigs(appConfigs);
25
+ const templateSource = compileTemplate__default.default(templateContent, {
26
+ interpolate: /<%=([\s\S]+?)%>/g
27
+ });
28
+ const publicPath = resolvePublicPath(config$1);
29
+ const indexHtmlContent = templateSource({
30
+ config: config$1,
31
+ publicPath
32
+ });
33
+ const indexHtmlContentWithConfig = indexHtmlContent.replace(
34
+ "</head>",
35
+ `
36
+ <script type="backstage.io/config">
37
+ ${JSON.stringify(appConfigs, null, 2).replaceAll("<\/script", "").replaceAll("<!--", "")}
38
+ <\/script>
39
+ </head>`
40
+ );
41
+ await fs__default.default.writeFile(
42
+ path.resolve(rootDir, "index.html"),
43
+ indexHtmlContentWithConfig,
44
+ "utf8"
45
+ );
46
+ return true;
47
+ }
48
+ function resolvePublicPath(config) {
49
+ const baseUrl = new URL(
50
+ config.getOptionalString("app.baseUrl") ?? "/",
51
+ "http://localhost:7007"
52
+ );
53
+ return baseUrl.pathname.replace(/\/+$/, "");
54
+ }
55
+
56
+ exports.injectConfigIntoHtml = injectConfigIntoHtml;
57
+ exports.resolvePublicPath = resolvePublicPath;
58
+ //# sourceMappingURL=injectConfigIntoHtml.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectConfigIntoHtml.cjs.js","sources":["../../../src/lib/config/injectConfigIntoHtml.ts"],"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 fs from 'fs-extra';\nimport { resolve as resolvePath } from 'path';\nimport { InjectOptions } from './types';\nimport compileTemplate from 'lodash/template';\nimport { Config, ConfigReader } from '@backstage/config';\n\nconst HTML_TEMPLATE_NAME = 'index.html.tmpl';\n\n/** @internal */\nexport async function injectConfigIntoHtml(\n options: InjectOptions,\n): Promise<boolean> {\n const { rootDir, appConfigs } = options;\n\n const templatePath = resolvePath(rootDir, HTML_TEMPLATE_NAME);\n\n if (!(await fs.exists(templatePath))) {\n return false;\n }\n\n const templateContent = await fs.readFile(\n resolvePath(rootDir, HTML_TEMPLATE_NAME),\n 'utf8',\n );\n\n const config = ConfigReader.fromConfigs(appConfigs);\n\n const templateSource = compileTemplate(templateContent, {\n interpolate: /<%=([\\s\\S]+?)%>/g,\n });\n\n const publicPath = resolvePublicPath(config);\n const indexHtmlContent = templateSource({\n config,\n publicPath,\n });\n\n const indexHtmlContentWithConfig = indexHtmlContent.replace(\n '</head>',\n `\n<script type=\"backstage.io/config\">\n${JSON.stringify(appConfigs, null, 2)\n // Note on the security aspects of this: We generally trust the app config to\n // be safe, since control of the app config effectively means full control of\n // the app. These substitutions are here as an extra precaution to avoid\n // unintentionally breaking the app, to avoid this being flagged, and in case\n // someone decides to hook up user input to the app config in their own setup.\n .replaceAll('</script', '')\n .replaceAll('<!--', '')}\n</script>\n</head>`,\n );\n\n await fs.writeFile(\n resolvePath(rootDir, 'index.html'),\n indexHtmlContentWithConfig,\n 'utf8',\n );\n\n return true;\n}\n\nexport function resolvePublicPath(config: Config) {\n const baseUrl = new URL(\n config.getOptionalString('app.baseUrl') ?? '/',\n 'http://localhost:7007',\n );\n return baseUrl.pathname.replace(/\\/+$/, '');\n}\n"],"names":["resolvePath","fs","config","ConfigReader","compileTemplate"],"mappings":";;;;;;;;;;;;AAsBA,MAAM,kBAAqB,GAAA,iBAAA,CAAA;AAG3B,eAAsB,qBACpB,OACkB,EAAA;AAClB,EAAM,MAAA,EAAE,OAAS,EAAA,UAAA,EAAe,GAAA,OAAA,CAAA;AAEhC,EAAM,MAAA,YAAA,GAAeA,YAAY,CAAA,OAAA,EAAS,kBAAkB,CAAA,CAAA;AAE5D,EAAA,IAAI,CAAE,MAAMC,mBAAG,CAAA,MAAA,CAAO,YAAY,CAAI,EAAA;AACpC,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAEA,EAAM,MAAA,eAAA,GAAkB,MAAMA,mBAAG,CAAA,QAAA;AAAA,IAC/BD,YAAA,CAAY,SAAS,kBAAkB,CAAA;AAAA,IACvC,MAAA;AAAA,GACF,CAAA;AAEA,EAAM,MAAAE,QAAA,GAASC,mBAAa,CAAA,WAAA,CAAY,UAAU,CAAA,CAAA;AAElD,EAAM,MAAA,cAAA,GAAiBC,iCAAgB,eAAiB,EAAA;AAAA,IACtD,WAAa,EAAA,kBAAA;AAAA,GACd,CAAA,CAAA;AAED,EAAM,MAAA,UAAA,GAAa,kBAAkBF,QAAM,CAAA,CAAA;AAC3C,EAAA,MAAM,mBAAmB,cAAe,CAAA;AAAA,YACtCA,QAAA;AAAA,IACA,UAAA;AAAA,GACD,CAAA,CAAA;AAED,EAAA,MAAM,6BAA6B,gBAAiB,CAAA,OAAA;AAAA,IAClD,SAAA;AAAA,IACA,CAAA;AAAA;AAAA,EAEF,IAAK,CAAA,SAAA,CAAU,UAAY,EAAA,IAAA,EAAM,CAAC,CAAA,CAMjC,UAAW,CAAA,WAAA,EAAY,EAAE,CAAA,CACzB,UAAW,CAAA,MAAA,EAAQ,EAAE,CAAC,CAAA;AAAA;AAAA,OAAA,CAAA;AAAA,GAGvB,CAAA;AAEA,EAAA,MAAMD,mBAAG,CAAA,SAAA;AAAA,IACPD,YAAA,CAAY,SAAS,YAAY,CAAA;AAAA,IACjC,0BAAA;AAAA,IACA,MAAA;AAAA,GACF,CAAA;AAEA,EAAO,OAAA,IAAA,CAAA;AACT,CAAA;AAEO,SAAS,kBAAkB,MAAgB,EAAA;AAChD,EAAA,MAAM,UAAU,IAAI,GAAA;AAAA,IAClB,MAAA,CAAO,iBAAkB,CAAA,aAAa,CAAK,IAAA,GAAA;AAAA,IAC3C,uBAAA;AAAA,GACF,CAAA;AACA,EAAA,OAAO,OAAQ,CAAA,QAAA,CAAS,OAAQ,CAAA,MAAA,EAAQ,EAAE,CAAA,CAAA;AAC5C;;;;;"}
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs-extra');
4
+ var path = require('path');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
9
+
10
+ async function injectConfigIntoStatic(options) {
11
+ const { staticDir, logger, appConfigs } = options;
12
+ const files = await fs__default.default.readdir(staticDir);
13
+ const jsFiles = files.filter((file) => file.endsWith(".js"));
14
+ const escapedData = JSON.stringify(appConfigs).replace(/("|'|\\)/g, "\\$1");
15
+ const injected = `/*__APP_INJECTED_CONFIG_MARKER__*/"${escapedData}"/*__INJECTED_END__*/`;
16
+ for (const jsFile of jsFiles) {
17
+ const path$1 = path.resolve(staticDir, jsFile);
18
+ const content = await fs__default.default.readFile(path$1, "utf8");
19
+ if (content.includes("__APP_INJECTED_RUNTIME_CONFIG__")) {
20
+ logger.info(`Injecting env config into ${jsFile}`);
21
+ const newContent = content.replaceAll(
22
+ '"__APP_INJECTED_RUNTIME_CONFIG__"',
23
+ injected
24
+ );
25
+ await fs__default.default.writeFile(path$1, newContent, "utf8");
26
+ return path$1;
27
+ } else if (content.includes("__APP_INJECTED_CONFIG_MARKER__")) {
28
+ logger.info(`Replacing injected env config in ${jsFile}`);
29
+ const newContent = content.replaceAll(
30
+ /\/\*__APP_INJECTED_CONFIG_MARKER__\*\/.*?\/\*__INJECTED_END__\*\//g,
31
+ injected
32
+ );
33
+ await fs__default.default.writeFile(path$1, newContent, "utf8");
34
+ return path$1;
35
+ }
36
+ }
37
+ logger.info("Env config not injected");
38
+ return void 0;
39
+ }
40
+
41
+ exports.injectConfigIntoStatic = injectConfigIntoStatic;
42
+ //# sourceMappingURL=injectConfigIntoStatic.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectConfigIntoStatic.cjs.js","sources":["../../../src/lib/config/injectConfigIntoStatic.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 fs from 'fs-extra';\nimport { resolve as resolvePath } from 'path';\nimport { InjectOptions } from './types';\n\n/**\n * Injects configs into the app bundle, replacing any existing injected config.\n */\nexport async function injectConfigIntoStatic(\n options: InjectOptions,\n): Promise<string | undefined> {\n const { staticDir, logger, appConfigs } = options;\n\n const files = await fs.readdir(staticDir);\n const jsFiles = files.filter(file => file.endsWith('.js'));\n\n const escapedData = JSON.stringify(appConfigs).replace(/(\"|'|\\\\)/g, '\\\\$1');\n const injected = `/*__APP_INJECTED_CONFIG_MARKER__*/\"${escapedData}\"/*__INJECTED_END__*/`;\n\n for (const jsFile of jsFiles) {\n const path = resolvePath(staticDir, jsFile);\n\n const content = await fs.readFile(path, 'utf8');\n if (content.includes('__APP_INJECTED_RUNTIME_CONFIG__')) {\n logger.info(`Injecting env config into ${jsFile}`);\n\n const newContent = content.replaceAll(\n '\"__APP_INJECTED_RUNTIME_CONFIG__\"',\n injected,\n );\n await fs.writeFile(path, newContent, 'utf8');\n return path;\n } else if (content.includes('__APP_INJECTED_CONFIG_MARKER__')) {\n logger.info(`Replacing injected env config in ${jsFile}`);\n\n const newContent = content.replaceAll(\n /\\/\\*__APP_INJECTED_CONFIG_MARKER__\\*\\/.*?\\/\\*__INJECTED_END__\\*\\//g,\n injected,\n );\n await fs.writeFile(path, newContent, 'utf8');\n return path;\n }\n }\n logger.info('Env config not injected');\n return undefined;\n}\n"],"names":["fs","path","resolvePath"],"mappings":";;;;;;;;;AAuBA,eAAsB,uBACpB,OAC6B,EAAA;AAC7B,EAAA,MAAM,EAAE,SAAA,EAAW,MAAQ,EAAA,UAAA,EAAe,GAAA,OAAA,CAAA;AAE1C,EAAA,MAAM,KAAQ,GAAA,MAAMA,mBAAG,CAAA,OAAA,CAAQ,SAAS,CAAA,CAAA;AACxC,EAAA,MAAM,UAAU,KAAM,CAAA,MAAA,CAAO,UAAQ,IAAK,CAAA,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AAEzD,EAAA,MAAM,cAAc,IAAK,CAAA,SAAA,CAAU,UAAU,CAAE,CAAA,OAAA,CAAQ,aAAa,MAAM,CAAA,CAAA;AAC1E,EAAM,MAAA,QAAA,GAAW,sCAAsC,WAAW,CAAA,qBAAA,CAAA,CAAA;AAElE,EAAA,KAAA,MAAW,UAAU,OAAS,EAAA;AAC5B,IAAM,MAAAC,MAAA,GAAOC,YAAY,CAAA,SAAA,EAAW,MAAM,CAAA,CAAA;AAE1C,IAAA,MAAM,OAAU,GAAA,MAAMF,mBAAG,CAAA,QAAA,CAASC,QAAM,MAAM,CAAA,CAAA;AAC9C,IAAI,IAAA,OAAA,CAAQ,QAAS,CAAA,iCAAiC,CAAG,EAAA;AACvD,MAAO,MAAA,CAAA,IAAA,CAAK,CAA6B,0BAAA,EAAA,MAAM,CAAE,CAAA,CAAA,CAAA;AAEjD,MAAA,MAAM,aAAa,OAAQ,CAAA,UAAA;AAAA,QACzB,mCAAA;AAAA,QACA,QAAA;AAAA,OACF,CAAA;AACA,MAAA,MAAMD,mBAAG,CAAA,SAAA,CAAUC,MAAM,EAAA,UAAA,EAAY,MAAM,CAAA,CAAA;AAC3C,MAAO,OAAAA,MAAA,CAAA;AAAA,KACE,MAAA,IAAA,OAAA,CAAQ,QAAS,CAAA,gCAAgC,CAAG,EAAA;AAC7D,MAAO,MAAA,CAAA,IAAA,CAAK,CAAoC,iCAAA,EAAA,MAAM,CAAE,CAAA,CAAA,CAAA;AAExD,MAAA,MAAM,aAAa,OAAQ,CAAA,UAAA;AAAA,QACzB,oEAAA;AAAA,QACA,QAAA;AAAA,OACF,CAAA;AACA,MAAA,MAAMD,mBAAG,CAAA,SAAA,CAAUC,MAAM,EAAA,UAAA,EAAY,MAAM,CAAA,CAAA;AAC3C,MAAO,OAAAA,MAAA,CAAA;AAAA,KACT;AAAA,GACF;AACA,EAAA,MAAA,CAAO,KAAK,yBAAyB,CAAA,CAAA;AACrC,EAAO,OAAA,KAAA,CAAA,CAAA;AACT;;;;"}
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs-extra');
4
+ var path = require('path');
5
+ var configLoader = require('@backstage/config-loader');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
10
+
11
+ async function readFrontendConfig(options) {
12
+ const { env, appDistDir, config } = options;
13
+ const schemaPath = path.resolve(appDistDir, ".config-schema.json");
14
+ if (await fs__default.default.pathExists(schemaPath)) {
15
+ const envConfigs = configLoader.readEnvConfig(env);
16
+ const serializedSchema = await fs__default.default.readJson(schemaPath);
17
+ try {
18
+ const schema = options.schema || await configLoader.loadConfigSchema({
19
+ serialized: serializedSchema
20
+ });
21
+ return await schema.process(
22
+ [...envConfigs, { data: config.get(), context: "app" }],
23
+ { visibility: ["frontend"], withDeprecatedKeys: true }
24
+ );
25
+ } catch (error) {
26
+ throw new Error(
27
+ `Invalid app bundle schema. If this error is unexpected you need to run \`yarn build\` in the app. If that doesn't help you should make sure your config schema is correct and rebuild the app bundle again. Caused by the following schema error, ${error}`
28
+ );
29
+ }
30
+ }
31
+ return [];
32
+ }
33
+
34
+ exports.readFrontendConfig = readFrontendConfig;
35
+ //# sourceMappingURL=readFrontendConfig.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readFrontendConfig.cjs.js","sources":["../../../src/lib/config/readFrontendConfig.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 fs from 'fs-extra';\nimport { resolve as resolvePath } from 'path';\nimport { AppConfig, Config } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport {\n ConfigSchema,\n loadConfigSchema,\n readEnvConfig,\n} from '@backstage/config-loader';\n\n/**\n * Read config from environment and process the backend config using the\n * schema that is embedded in the frontend build.\n */\nexport async function readFrontendConfig(options: {\n env: { [name: string]: string | undefined };\n appDistDir: string;\n config: Config;\n schema?: ConfigSchema;\n}): Promise<AppConfig[]> {\n const { env, appDistDir, config } = options;\n\n const schemaPath = resolvePath(appDistDir, '.config-schema.json');\n if (await fs.pathExists(schemaPath)) {\n const envConfigs = readEnvConfig(env);\n const serializedSchema = await fs.readJson(schemaPath);\n\n try {\n const schema =\n options.schema ||\n (await loadConfigSchema({\n serialized: serializedSchema,\n }));\n\n return await schema.process(\n [...envConfigs, { data: config.get() as JsonObject, context: 'app' }],\n { visibility: ['frontend'], withDeprecatedKeys: true },\n );\n } catch (error) {\n throw new Error(\n 'Invalid app bundle schema. If this error is unexpected you need to run `yarn build` in the app. ' +\n `If that doesn't help you should make sure your config schema is correct and rebuild the app bundle again. ` +\n `Caused by the following schema error, ${error}`,\n );\n }\n }\n\n return [];\n}\n"],"names":["resolvePath","fs","readEnvConfig","loadConfigSchema"],"mappings":";;;;;;;;;;AA8BA,eAAsB,mBAAmB,OAKhB,EAAA;AACvB,EAAA,MAAM,EAAE,GAAA,EAAK,UAAY,EAAA,MAAA,EAAW,GAAA,OAAA,CAAA;AAEpC,EAAM,MAAA,UAAA,GAAaA,YAAY,CAAA,UAAA,EAAY,qBAAqB,CAAA,CAAA;AAChE,EAAA,IAAI,MAAMC,mBAAA,CAAG,UAAW,CAAA,UAAU,CAAG,EAAA;AACnC,IAAM,MAAA,UAAA,GAAaC,2BAAc,GAAG,CAAA,CAAA;AACpC,IAAA,MAAM,gBAAmB,GAAA,MAAMD,mBAAG,CAAA,QAAA,CAAS,UAAU,CAAA,CAAA;AAErD,IAAI,IAAA;AACF,MAAA,MAAM,MACJ,GAAA,OAAA,CAAQ,MACP,IAAA,MAAME,6BAAiB,CAAA;AAAA,QACtB,UAAY,EAAA,gBAAA;AAAA,OACb,CAAA,CAAA;AAEH,MAAA,OAAO,MAAM,MAAO,CAAA,OAAA;AAAA,QAClB,CAAC,GAAG,UAAA,EAAY,EAAE,IAAA,EAAM,OAAO,GAAI,EAAA,EAAiB,OAAS,EAAA,KAAA,EAAO,CAAA;AAAA,QACpE,EAAE,UAAY,EAAA,CAAC,UAAU,CAAA,EAAG,oBAAoB,IAAK,EAAA;AAAA,OACvD,CAAA;AAAA,aACO,KAAO,EAAA;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,qPAE2C,KAAK,CAAA,CAAA;AAAA,OAClD,CAAA;AAAA,KACF;AAAA,GACF;AAEA,EAAA,OAAO,EAAC,CAAA;AACV;;;;"}