@backstage/backend-test-utils 1.11.2-next.1 → 1.11.2

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # @backstage/backend-test-utils
2
2
 
3
+ ## 1.11.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
8
+ - f44c6bd: Deduplicated internal readiness-polling helpers used by the database and cache test infrastructure.
9
+ - Updated dependencies
10
+ - @backstage/backend-plugin-api@1.9.0
11
+ - @backstage/backend-defaults@0.17.0
12
+ - @backstage/errors@1.3.0
13
+ - @backstage/plugin-auth-node@0.7.0
14
+ - @backstage/backend-app-api@1.6.1
15
+ - @backstage/config@1.3.7
16
+ - @backstage/plugin-events-node@0.4.21
17
+ - @backstage/plugin-permission-common@0.9.8
18
+
19
+ ## 1.11.2-next.2
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @backstage/errors@1.3.0-next.0
25
+ - @backstage/plugin-auth-node@0.7.0-next.2
26
+ - @backstage/backend-app-api@1.6.1-next.2
27
+ - @backstage/backend-defaults@0.16.1-next.2
28
+ - @backstage/backend-plugin-api@1.9.0-next.2
29
+ - @backstage/config@1.3.7-next.0
30
+ - @backstage/plugin-events-node@0.4.21-next.2
31
+ - @backstage/plugin-permission-common@0.9.8-next.0
32
+
3
33
  ## 1.11.2-next.1
4
34
 
5
35
  ### Patch Changes
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ var Keyv = require('keyv');
4
+ var uuid = require('uuid');
5
+ var waitForReady = require('../util/waitForReady.cjs.js');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var Keyv__default = /*#__PURE__*/_interopDefaultCompat(Keyv);
10
+
11
+ async function attemptKeyvConnection(createStore, connection, label) {
12
+ let keyv;
13
+ await waitForReady.waitForReady(async () => {
14
+ const store = createStore(connection);
15
+ const attemptKeyv = new Keyv__default.default({ store });
16
+ let succeeded = false;
17
+ try {
18
+ const value = uuid.v4();
19
+ await attemptKeyv.set("test", value);
20
+ succeeded = await attemptKeyv.get("test") === value;
21
+ if (succeeded) {
22
+ keyv = attemptKeyv;
23
+ }
24
+ return succeeded;
25
+ } finally {
26
+ if (!succeeded) {
27
+ await attemptKeyv.disconnect();
28
+ }
29
+ }
30
+ }, label);
31
+ return keyv;
32
+ }
33
+ async function startRedisLikeContainer(image, store, createStore) {
34
+ const { GenericContainer } = require("testcontainers");
35
+ const container = await new GenericContainer(image).withExposedPorts(6379).start();
36
+ const host = container.getHost();
37
+ const port = container.getMappedPort(6379);
38
+ const connection = `redis://${host}:${port}`;
39
+ const keyv = await attemptKeyvConnection(createStore, connection, store);
40
+ return {
41
+ store,
42
+ connection,
43
+ keyv,
44
+ stop: async () => {
45
+ await keyv.disconnect();
46
+ await container.stop({ timeout: 1e4 });
47
+ }
48
+ };
49
+ }
50
+
51
+ exports.attemptKeyvConnection = attemptKeyvConnection;
52
+ exports.startRedisLikeContainer = startRedisLikeContainer;
53
+ //# sourceMappingURL=helpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../src/cache/helpers.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 Keyv, { type KeyvStoreAdapter } from 'keyv';\nimport { v4 as uuid } from 'uuid';\nimport { waitForReady } from '../util/waitForReady';\nimport { Instance } from './types';\n\n/**\n * Polls a Keyv store until a set/get round-trip succeeds.\n */\nexport async function attemptKeyvConnection(\n createStore: (connection: string) => KeyvStoreAdapter,\n connection: string,\n label: string,\n): Promise<Keyv> {\n let keyv: Keyv | undefined;\n\n await waitForReady(async () => {\n const store = createStore(connection);\n const attemptKeyv = new Keyv({ store });\n let succeeded = false;\n\n try {\n const value = uuid();\n await attemptKeyv.set('test', value);\n succeeded = (await attemptKeyv.get('test')) === value;\n if (succeeded) {\n keyv = attemptKeyv;\n }\n return succeeded;\n } finally {\n if (!succeeded) {\n await attemptKeyv.disconnect();\n }\n }\n }, label);\n\n return keyv!;\n}\n\n/**\n * Starts a Redis-protocol-compatible container (Redis, Valkey, etc.) on port\n * 6379 and waits until a Keyv round-trip succeeds.\n */\nexport async function startRedisLikeContainer(\n image: string,\n store: string,\n createStore: (connection: string) => KeyvStoreAdapter,\n): Promise<Instance> {\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(6379)\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(6379);\n const connection = `redis://${host}:${port}`;\n\n const keyv = await attemptKeyvConnection(createStore, connection, store);\n\n return {\n store,\n connection,\n keyv,\n stop: async () => {\n await keyv.disconnect();\n await container.stop({ timeout: 10_000 });\n },\n };\n}\n"],"names":["waitForReady","Keyv","uuid"],"mappings":";;;;;;;;;;AAwBA,eAAsB,qBAAA,CACpB,WAAA,EACA,UAAA,EACA,KAAA,EACe;AACf,EAAA,IAAI,IAAA;AAEJ,EAAA,MAAMA,0BAAa,YAAY;AAC7B,IAAA,MAAM,KAAA,GAAQ,YAAY,UAAU,CAAA;AACpC,IAAA,MAAM,WAAA,GAAc,IAAIC,qBAAA,CAAK,EAAE,OAAO,CAAA;AACtC,IAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAA,IAAI;AACF,MAAA,MAAM,QAAQC,OAAA,EAAK;AACnB,MAAA,MAAM,WAAA,CAAY,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AACnC,MAAA,SAAA,GAAa,MAAM,WAAA,CAAY,GAAA,CAAI,MAAM,CAAA,KAAO,KAAA;AAChD,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,IAAA,GAAO,WAAA;AAAA,MACT;AACA,MAAA,OAAO,SAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,MAAM,YAAY,UAAA,EAAW;AAAA,MAC/B;AAAA,IACF;AAAA,EACF,GAAG,KAAK,CAAA;AAER,EAAA,OAAO,IAAA;AACT;AAMA,eAAsB,uBAAA,CACpB,KAAA,EACA,KAAA,EACA,WAAA,EACmB;AAEnB,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,IAAI,CAAA,CACrB,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,IAAI,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,CAAA,QAAA,EAAW,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAE1C,EAAA,MAAM,IAAA,GAAO,MAAM,qBAAA,CAAsB,WAAA,EAAa,YAAY,KAAK,CAAA;AAEvE,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAM,YAAY;AAChB,MAAA,MAAM,KAAK,UAAA,EAAW;AACtB,MAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,IAC1C;AAAA,GACF;AACF;;;;;"}
@@ -1,37 +1,19 @@
1
1
  'use strict';
2
2
 
3
- var Keyv = require('keyv');
4
3
  var KeyvMemcache = require('@keyv/memcache');
5
- var uuid = require('uuid');
4
+ var helpers = require('./helpers.cjs.js');
6
5
 
7
6
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
7
 
9
- var Keyv__default = /*#__PURE__*/_interopDefaultCompat(Keyv);
10
8
  var KeyvMemcache__default = /*#__PURE__*/_interopDefaultCompat(KeyvMemcache);
11
9
 
12
- async function attemptMemcachedConnection(connection) {
13
- const startTime = Date.now();
14
- for (; ; ) {
15
- try {
16
- const store = new KeyvMemcache__default.default(connection);
17
- const keyv = new Keyv__default.default({ store });
18
- const value = uuid.v4();
19
- await keyv.set("test", value);
20
- if (await keyv.get("test") === value) {
21
- return keyv;
22
- }
23
- } catch (e) {
24
- if (Date.now() - startTime > 3e4) {
25
- throw new Error(
26
- `Timed out waiting for memcached to be ready for connections, ${e}`
27
- );
28
- }
29
- }
30
- await new Promise((resolve) => setTimeout(resolve, 100));
31
- }
32
- }
10
+ const createStore = (connection) => new KeyvMemcache__default.default(connection);
33
11
  async function connectToExternalMemcache(connection) {
34
- const keyv = await attemptMemcachedConnection(connection);
12
+ const keyv = await helpers.attemptKeyvConnection(
13
+ createStore,
14
+ connection,
15
+ "memcached"
16
+ );
35
17
  return {
36
18
  store: "memcache",
37
19
  connection,
@@ -45,7 +27,11 @@ async function startMemcachedContainer(image) {
45
27
  const host = container.getHost();
46
28
  const port = container.getMappedPort(11211);
47
29
  const connection = `${host}:${port}`;
48
- const keyv = await attemptMemcachedConnection(connection);
30
+ const keyv = await helpers.attemptKeyvConnection(
31
+ createStore,
32
+ connection,
33
+ "memcached"
34
+ );
49
35
  return {
50
36
  store: "memcache",
51
37
  connection,
@@ -1 +1 @@
1
- {"version":3,"file":"memcache.cjs.js","sources":["../../src/cache/memcache.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 Keyv from 'keyv';\nimport KeyvMemcache from '@keyv/memcache';\nimport { v4 as uuid } from 'uuid';\nimport { Instance } from './types';\n\nasync function attemptMemcachedConnection(connection: string): Promise<Keyv> {\n const startTime = Date.now();\n\n for (;;) {\n try {\n const store = new KeyvMemcache(connection);\n const keyv = new Keyv({ store });\n const value = uuid();\n await keyv.set('test', value);\n if ((await keyv.get('test')) === value) {\n return keyv;\n }\n } catch (e) {\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for memcached to be ready for connections, ${e}`,\n );\n }\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function connectToExternalMemcache(\n connection: string,\n): Promise<Instance> {\n const keyv = await attemptMemcachedConnection(connection);\n return {\n store: 'memcache',\n connection,\n keyv,\n stop: async () => await keyv.disconnect(),\n };\n}\n\nexport async function startMemcachedContainer(\n image: string,\n): Promise<Instance> {\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(11211)\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(11211);\n const connection = `${host}:${port}`;\n\n const keyv = await attemptMemcachedConnection(connection);\n\n return {\n store: 'memcache',\n connection,\n keyv,\n stop: async () => {\n await keyv.disconnect();\n await container.stop({ timeout: 10_000 });\n },\n };\n}\n"],"names":["KeyvMemcache","Keyv","uuid"],"mappings":";;;;;;;;;;;AAqBA,eAAe,2BAA2B,UAAA,EAAmC;AAC3E,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,WAAS;AACP,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,IAAIA,6BAAA,CAAa,UAAU,CAAA;AACzC,MAAA,MAAM,IAAA,GAAO,IAAIC,qBAAA,CAAK,EAAE,OAAO,CAAA;AAC/B,MAAA,MAAM,QAAQC,OAAA,EAAK;AACnB,MAAA,MAAM,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AAC5B,MAAA,IAAK,MAAM,IAAA,CAAK,GAAA,CAAI,MAAM,MAAO,KAAA,EAAO;AACtC,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAA,EAAQ;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,gEAAgE,CAAC,CAAA;AAAA,SACnE;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAAA,EACvD;AACF;AAEA,eAAsB,0BACpB,UAAA,EACmB;AACnB,EAAA,MAAM,IAAA,GAAO,MAAM,0BAAA,CAA2B,UAAU,CAAA;AACxD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,YAAY,MAAM,IAAA,CAAK,UAAA;AAAW,GAC1C;AACF;AAEA,eAAsB,wBACpB,KAAA,EACmB;AAEnB,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,KAAK,CAAA,CACtB,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,KAAK,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAElC,EAAA,MAAM,IAAA,GAAO,MAAM,0BAAA,CAA2B,UAAU,CAAA;AAExD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAM,YAAY;AAChB,MAAA,MAAM,KAAK,UAAA,EAAW;AACtB,MAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,IAC1C;AAAA,GACF;AACF;;;;;"}
1
+ {"version":3,"file":"memcache.cjs.js","sources":["../../src/cache/memcache.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 KeyvMemcache from '@keyv/memcache';\nimport { Instance } from './types';\nimport { attemptKeyvConnection } from './helpers';\n\nconst createStore = (connection: string) => new KeyvMemcache(connection);\n\nexport async function connectToExternalMemcache(\n connection: string,\n): Promise<Instance> {\n const keyv = await attemptKeyvConnection(\n createStore,\n connection,\n 'memcached',\n );\n return {\n store: 'memcache',\n connection,\n keyv,\n stop: async () => await keyv.disconnect(),\n };\n}\n\nexport async function startMemcachedContainer(\n image: string,\n): Promise<Instance> {\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(11211)\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(11211);\n const connection = `${host}:${port}`;\n\n const keyv = await attemptKeyvConnection(\n createStore,\n connection,\n 'memcached',\n );\n\n return {\n store: 'memcache',\n connection,\n keyv,\n stop: async () => {\n await keyv.disconnect();\n await container.stop({ timeout: 10_000 });\n },\n };\n}\n"],"names":["KeyvMemcache","attemptKeyvConnection"],"mappings":";;;;;;;;;AAoBA,MAAM,WAAA,GAAc,CAAC,UAAA,KAAuB,IAAIA,8BAAa,UAAU,CAAA;AAEvE,eAAsB,0BACpB,UAAA,EACmB;AACnB,EAAA,MAAM,OAAO,MAAMC,6BAAA;AAAA,IACjB,WAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,YAAY,MAAM,IAAA,CAAK,UAAA;AAAW,GAC1C;AACF;AAEA,eAAsB,wBACpB,KAAA,EACmB;AAEnB,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,KAAK,CAAA,CACtB,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,KAAK,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAElC,EAAA,MAAM,OAAO,MAAMA,6BAAA;AAAA,IACjB,WAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAM,YAAY;AAChB,MAAA,MAAM,KAAK,UAAA,EAAW;AACtB,MAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,IAC1C;AAAA,GACF;AACF;;;;;"}
@@ -1,37 +1,15 @@
1
1
  'use strict';
2
2
 
3
- var Keyv = require('keyv');
4
3
  var KeyvRedis = require('@keyv/redis');
5
- var uuid = require('uuid');
4
+ var helpers = require('./helpers.cjs.js');
6
5
 
7
6
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
7
 
9
- var Keyv__default = /*#__PURE__*/_interopDefaultCompat(Keyv);
10
8
  var KeyvRedis__default = /*#__PURE__*/_interopDefaultCompat(KeyvRedis);
11
9
 
12
- async function attemptRedisConnection(connection) {
13
- const startTime = Date.now();
14
- for (; ; ) {
15
- try {
16
- const store = new KeyvRedis__default.default(connection);
17
- const keyv = new Keyv__default.default({ store });
18
- const value = uuid.v4();
19
- await keyv.set("test", value);
20
- if (await keyv.get("test") === value) {
21
- return keyv;
22
- }
23
- } catch (e) {
24
- if (Date.now() - startTime > 3e4) {
25
- throw new Error(
26
- `Timed out waiting for redis to be ready for connections, ${e}`
27
- );
28
- }
29
- }
30
- await new Promise((resolve) => setTimeout(resolve, 100));
31
- }
32
- }
10
+ const createStore = (connection) => new KeyvRedis__default.default(connection);
33
11
  async function connectToExternalRedis(connection) {
34
- const keyv = await attemptRedisConnection(connection);
12
+ const keyv = await helpers.attemptKeyvConnection(createStore, connection, "redis");
35
13
  return {
36
14
  store: "redis",
37
15
  connection,
@@ -40,21 +18,7 @@ async function connectToExternalRedis(connection) {
40
18
  };
41
19
  }
42
20
  async function startRedisContainer(image) {
43
- const { GenericContainer } = require("testcontainers");
44
- const container = await new GenericContainer(image).withExposedPorts(6379).start();
45
- const host = container.getHost();
46
- const port = container.getMappedPort(6379);
47
- const connection = `redis://${host}:${port}`;
48
- const keyv = await attemptRedisConnection(connection);
49
- return {
50
- store: "redis",
51
- connection,
52
- keyv,
53
- stop: async () => {
54
- await keyv.disconnect();
55
- await container.stop({ timeout: 1e4 });
56
- }
57
- };
21
+ return helpers.startRedisLikeContainer(image, "redis", createStore);
58
22
  }
59
23
 
60
24
  exports.connectToExternalRedis = connectToExternalRedis;
@@ -1 +1 @@
1
- {"version":3,"file":"redis.cjs.js","sources":["../../src/cache/redis.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 Keyv from 'keyv';\nimport KeyvRedis from '@keyv/redis';\nimport { v4 as uuid } from 'uuid';\nimport { Instance } from './types';\n\nasync function attemptRedisConnection(connection: string): Promise<Keyv> {\n const startTime = Date.now();\n\n for (;;) {\n try {\n const store = new KeyvRedis(connection);\n const keyv = new Keyv({ store });\n const value = uuid();\n await keyv.set('test', value);\n if ((await keyv.get('test')) === value) {\n return keyv;\n }\n } catch (e) {\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for redis to be ready for connections, ${e}`,\n );\n }\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function connectToExternalRedis(\n connection: string,\n): Promise<Instance> {\n const keyv = await attemptRedisConnection(connection);\n return {\n store: 'redis',\n connection,\n keyv,\n stop: async () => await keyv.disconnect(),\n };\n}\n\nexport async function startRedisContainer(image: string): Promise<Instance> {\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(6379)\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(6379);\n const connection = `redis://${host}:${port}`;\n\n const keyv = await attemptRedisConnection(connection);\n\n return {\n store: 'redis',\n connection,\n keyv,\n stop: async () => {\n await keyv.disconnect();\n await container.stop({ timeout: 10_000 });\n },\n };\n}\n"],"names":["KeyvRedis","Keyv","uuid"],"mappings":";;;;;;;;;;;AAqBA,eAAe,uBAAuB,UAAA,EAAmC;AACvE,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,WAAS;AACP,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,IAAIA,0BAAA,CAAU,UAAU,CAAA;AACtC,MAAA,MAAM,IAAA,GAAO,IAAIC,qBAAA,CAAK,EAAE,OAAO,CAAA;AAC/B,MAAA,MAAM,QAAQC,OAAA,EAAK;AACnB,MAAA,MAAM,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AAC5B,MAAA,IAAK,MAAM,IAAA,CAAK,GAAA,CAAI,MAAM,MAAO,KAAA,EAAO;AACtC,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAA,EAAQ;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,4DAA4D,CAAC,CAAA;AAAA,SAC/D;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAAA,EACvD;AACF;AAEA,eAAsB,uBACpB,UAAA,EACmB;AACnB,EAAA,MAAM,IAAA,GAAO,MAAM,sBAAA,CAAuB,UAAU,CAAA;AACpD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,YAAY,MAAM,IAAA,CAAK,UAAA;AAAW,GAC1C;AACF;AAEA,eAAsB,oBAAoB,KAAA,EAAkC;AAE1E,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,IAAI,CAAA,CACrB,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,IAAI,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,CAAA,QAAA,EAAW,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAE1C,EAAA,MAAM,IAAA,GAAO,MAAM,sBAAA,CAAuB,UAAU,CAAA;AAEpD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAM,YAAY;AAChB,MAAA,MAAM,KAAK,UAAA,EAAW;AACtB,MAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,IAC1C;AAAA,GACF;AACF;;;;;"}
1
+ {"version":3,"file":"redis.cjs.js","sources":["../../src/cache/redis.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 KeyvRedis from '@keyv/redis';\nimport { Instance } from './types';\nimport { attemptKeyvConnection, startRedisLikeContainer } from './helpers';\n\nconst createStore = (connection: string) => new KeyvRedis(connection);\n\nexport async function connectToExternalRedis(\n connection: string,\n): Promise<Instance> {\n const keyv = await attemptKeyvConnection(createStore, connection, 'redis');\n return {\n store: 'redis',\n connection,\n keyv,\n stop: async () => await keyv.disconnect(),\n };\n}\n\nexport async function startRedisContainer(image: string): Promise<Instance> {\n return startRedisLikeContainer(image, 'redis', createStore);\n}\n"],"names":["KeyvRedis","attemptKeyvConnection","startRedisLikeContainer"],"mappings":";;;;;;;;;AAoBA,MAAM,WAAA,GAAc,CAAC,UAAA,KAAuB,IAAIA,2BAAU,UAAU,CAAA;AAEpE,eAAsB,uBACpB,UAAA,EACmB;AACnB,EAAA,MAAM,IAAA,GAAO,MAAMC,6BAAA,CAAsB,WAAA,EAAa,YAAY,OAAO,CAAA;AACzE,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,YAAY,MAAM,IAAA,CAAK,UAAA;AAAW,GAC1C;AACF;AAEA,eAAsB,oBAAoB,KAAA,EAAkC;AAC1E,EAAA,OAAOC,+BAAA,CAAwB,KAAA,EAAO,OAAA,EAAS,WAAW,CAAA;AAC5D;;;;;"}
@@ -1,37 +1,15 @@
1
1
  'use strict';
2
2
 
3
- var Keyv = require('keyv');
4
3
  var KeyvValkey = require('@keyv/valkey');
5
- var uuid = require('uuid');
4
+ var helpers = require('./helpers.cjs.js');
6
5
 
7
6
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
7
 
9
- var Keyv__default = /*#__PURE__*/_interopDefaultCompat(Keyv);
10
8
  var KeyvValkey__default = /*#__PURE__*/_interopDefaultCompat(KeyvValkey);
11
9
 
12
- async function attemptValkeyConnection(connection) {
13
- const startTime = Date.now();
14
- for (; ; ) {
15
- try {
16
- const store = new KeyvValkey__default.default(connection);
17
- const keyv = new Keyv__default.default({ store });
18
- const value = uuid.v4();
19
- await keyv.set("test", value);
20
- if (await keyv.get("test") === value) {
21
- return keyv;
22
- }
23
- } catch (e) {
24
- if (Date.now() - startTime > 3e4) {
25
- throw new Error(
26
- `Timed out waiting for valkey to be ready for connections, ${e}`
27
- );
28
- }
29
- }
30
- await new Promise((resolve) => setTimeout(resolve, 100));
31
- }
32
- }
10
+ const createStore = (connection) => new KeyvValkey__default.default(connection);
33
11
  async function connectToExternalValkey(connection) {
34
- const keyv = await attemptValkeyConnection(connection);
12
+ const keyv = await helpers.attemptKeyvConnection(createStore, connection, "valkey");
35
13
  return {
36
14
  store: "valkey",
37
15
  connection,
@@ -40,21 +18,7 @@ async function connectToExternalValkey(connection) {
40
18
  };
41
19
  }
42
20
  async function startValkeyContainer(image) {
43
- const { GenericContainer } = require("testcontainers");
44
- const container = await new GenericContainer(image).withExposedPorts(6379).start();
45
- const host = container.getHost();
46
- const port = container.getMappedPort(6379);
47
- const connection = `redis://${host}:${port}`;
48
- const keyv = await attemptValkeyConnection(connection);
49
- return {
50
- store: "valkey",
51
- connection,
52
- keyv,
53
- stop: async () => {
54
- await keyv.disconnect();
55
- await container.stop({ timeout: 1e4 });
56
- }
57
- };
21
+ return helpers.startRedisLikeContainer(image, "valkey", createStore);
58
22
  }
59
23
 
60
24
  exports.connectToExternalValkey = connectToExternalValkey;
@@ -1 +1 @@
1
- {"version":3,"file":"valkey.cjs.js","sources":["../../src/cache/valkey.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 Keyv from 'keyv';\nimport KeyvValkey from '@keyv/valkey';\nimport { v4 as uuid } from 'uuid';\nimport { Instance } from './types';\n\nasync function attemptValkeyConnection(connection: string): Promise<Keyv> {\n const startTime = Date.now();\n\n for (;;) {\n try {\n const store = new KeyvValkey(connection);\n const keyv = new Keyv({ store });\n const value = uuid();\n await keyv.set('test', value);\n if ((await keyv.get('test')) === value) {\n return keyv;\n }\n } catch (e) {\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for valkey to be ready for connections, ${e}`,\n );\n }\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function connectToExternalValkey(\n connection: string,\n): Promise<Instance> {\n const keyv = await attemptValkeyConnection(connection);\n return {\n store: 'valkey',\n connection,\n keyv,\n stop: async () => await keyv.disconnect(),\n };\n}\n\nexport async function startValkeyContainer(image: string): Promise<Instance> {\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(6379)\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(6379);\n const connection = `redis://${host}:${port}`;\n\n const keyv = await attemptValkeyConnection(connection);\n\n return {\n store: 'valkey',\n connection,\n keyv,\n stop: async () => {\n await keyv.disconnect();\n await container.stop({ timeout: 10_000 });\n },\n };\n}\n"],"names":["KeyvValkey","Keyv","uuid"],"mappings":";;;;;;;;;;;AAqBA,eAAe,wBAAwB,UAAA,EAAmC;AACxE,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,WAAS;AACP,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,IAAIA,2BAAA,CAAW,UAAU,CAAA;AACvC,MAAA,MAAM,IAAA,GAAO,IAAIC,qBAAA,CAAK,EAAE,OAAO,CAAA;AAC/B,MAAA,MAAM,QAAQC,OAAA,EAAK;AACnB,MAAA,MAAM,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AAC5B,MAAA,IAAK,MAAM,IAAA,CAAK,GAAA,CAAI,MAAM,MAAO,KAAA,EAAO;AACtC,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAA,EAAQ;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,6DAA6D,CAAC,CAAA;AAAA,SAChE;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAAA,EACvD;AACF;AAEA,eAAsB,wBACpB,UAAA,EACmB;AACnB,EAAA,MAAM,IAAA,GAAO,MAAM,uBAAA,CAAwB,UAAU,CAAA;AACrD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,QAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,YAAY,MAAM,IAAA,CAAK,UAAA;AAAW,GAC1C;AACF;AAEA,eAAsB,qBAAqB,KAAA,EAAkC;AAE3E,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,IAAI,CAAA,CACrB,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,IAAI,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,CAAA,QAAA,EAAW,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAE1C,EAAA,MAAM,IAAA,GAAO,MAAM,uBAAA,CAAwB,UAAU,CAAA;AAErD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,QAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAM,YAAY;AAChB,MAAA,MAAM,KAAK,UAAA,EAAW;AACtB,MAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,IAC1C;AAAA,GACF;AACF;;;;;"}
1
+ {"version":3,"file":"valkey.cjs.js","sources":["../../src/cache/valkey.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 KeyvValkey from '@keyv/valkey';\nimport { Instance } from './types';\nimport { attemptKeyvConnection, startRedisLikeContainer } from './helpers';\n\nconst createStore = (connection: string) => new KeyvValkey(connection);\n\nexport async function connectToExternalValkey(\n connection: string,\n): Promise<Instance> {\n const keyv = await attemptKeyvConnection(createStore, connection, 'valkey');\n return {\n store: 'valkey',\n connection,\n keyv,\n stop: async () => await keyv.disconnect(),\n };\n}\n\nexport async function startValkeyContainer(image: string): Promise<Instance> {\n return startRedisLikeContainer(image, 'valkey', createStore);\n}\n"],"names":["KeyvValkey","attemptKeyvConnection","startRedisLikeContainer"],"mappings":";;;;;;;;;AAoBA,MAAM,WAAA,GAAc,CAAC,UAAA,KAAuB,IAAIA,4BAAW,UAAU,CAAA;AAErE,eAAsB,wBACpB,UAAA,EACmB;AACnB,EAAA,MAAM,IAAA,GAAO,MAAMC,6BAAA,CAAsB,WAAA,EAAa,YAAY,QAAQ,CAAA;AAC1E,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,QAAA;AAAA,IACP,UAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,EAAM,YAAY,MAAM,IAAA,CAAK,UAAA;AAAW,GAC1C;AACF;AAEA,eAAsB,qBAAqB,KAAA,EAAkC;AAC3E,EAAA,OAAOC,+BAAA,CAAwB,KAAA,EAAO,QAAA,EAAU,WAAW,CAAA;AAC7D;;;;;"}
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var errors = require('@backstage/errors');
4
3
  var node_crypto = require('node:crypto');
5
4
  var knexFactory = require('knex');
6
5
  var uuid = require('uuid');
7
6
  var yn = require('yn');
7
+ var waitForReady = require('../util/waitForReady.cjs.js');
8
8
  var types = require('./types.cjs.js');
9
9
 
10
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
@@ -13,36 +13,21 @@ var knexFactory__default = /*#__PURE__*/_interopDefaultCompat(knexFactory);
13
13
  var yn__default = /*#__PURE__*/_interopDefaultCompat(yn);
14
14
 
15
15
  async function waitForMysqlReady(connection) {
16
- const startTime = Date.now();
17
- let lastError;
18
- let attempts = 0;
19
- for (; ; ) {
20
- attempts += 1;
21
- let knex;
16
+ await waitForReady.waitForReady(async () => {
17
+ const knex = knexFactory__default.default({
18
+ client: "mysql2",
19
+ connection: {
20
+ // make a copy because the driver mutates this
21
+ ...connection
22
+ }
23
+ });
22
24
  try {
23
- knex = knexFactory__default.default({
24
- client: "mysql2",
25
- connection: {
26
- // make a copy because the driver mutates this
27
- ...connection
28
- }
29
- });
30
25
  const result = await knex.select(knex.raw("version() AS version"));
31
- if (Array.isArray(result) && result[0]?.version) {
32
- return;
33
- }
34
- } catch (e) {
35
- lastError = e;
26
+ return Array.isArray(result) && Boolean(result[0]?.version);
36
27
  } finally {
37
- await knex?.destroy();
38
- }
39
- if (Date.now() - startTime > 3e4) {
40
- throw new Error(
41
- `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${lastError ? `last error was ${errors.stringifyError(lastError)}` : "(no errors thrown)"}`
42
- );
28
+ await knex.destroy();
43
29
  }
44
- await new Promise((resolve) => setTimeout(resolve, 100));
45
- }
30
+ }, "the database");
46
31
  }
47
32
  async function startMysqlContainer(image) {
48
33
  const user = "root";
@@ -1 +1 @@
1
- {"version":3,"file":"mysql.cjs.js","sources":["../../src/database/mysql.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 { stringifyError } from '@backstage/errors';\nimport { randomBytes } from 'node:crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport yn from 'yn';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForMysqlReady(\n connection: Knex.MySqlConnectionConfig,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: Error | undefined;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n let knex: Knex | undefined;\n try {\n knex = knexFactory({\n client: 'mysql2',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n const result = await knex.select(knex.raw('version() AS version'));\n if (Array.isArray(result) && result[0]?.version) {\n return;\n }\n } catch (e) {\n lastError = e;\n } finally {\n await knex?.destroy();\n }\n\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function startMysqlContainer(image: string): Promise<{\n connection: Knex.MySqlConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'root';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(3306)\n .withEnvironment({ MYSQL_ROOT_PASSWORD: password })\n .withTmpFs({ '/var/lib/mysql': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(3306);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForMysqlReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport function parseMysqlConnectionString(\n connectionString: string,\n): Knex.MySqlConnectionConfig {\n try {\n const {\n protocol,\n username,\n password,\n port,\n hostname,\n pathname,\n searchParams,\n } = new URL(connectionString);\n\n if (protocol !== 'mysql:') {\n throw new Error(`Unknown protocol ${protocol}`);\n } else if (!username || !password) {\n throw new Error(`Missing username/password`);\n } else if (!pathname.match(/^\\/[^/]+$/)) {\n throw new Error(`Expected single path segment`);\n }\n\n const result: Knex.MySqlConnectionConfig = {\n user: username,\n password,\n host: hostname,\n port: Number(port || 3306),\n database: decodeURIComponent(pathname.substring(1)),\n };\n\n const ssl = searchParams.get('ssl');\n if (ssl) {\n result.ssl = ssl;\n }\n\n const debug = searchParams.get('debug');\n if (debug) {\n result.debug = yn(debug);\n }\n\n return result;\n } catch (e) {\n throw new Error(`Error while parsing MySQL connection string, ${e}`, e);\n }\n}\n\nexport class MysqlEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<MysqlEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parseMysqlConnectionString(connectionString);\n return new MysqlEngine(\n properties,\n connection as Knex.MySqlConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startMysqlContainer(\n dockerImageName,\n );\n return new MysqlEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.MySqlConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.MySqlConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n const connection = {\n ...this.#connection,\n database: null as unknown as string,\n };\n return knexFactory({\n client: this.#properties.driver,\n connection,\n pool: {\n min: 0,\n max: 1,\n acquireTimeoutMillis: 20_000,\n createTimeoutMillis: 20_000,\n createRetryIntervalMillis: 1_000,\n },\n });\n }\n}\n"],"names":["knexFactory","stringifyError","uuid","yn","randomBytes","LARGER_POOL_CONFIG"],"mappings":"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"}
1
+ {"version":3,"file":"mysql.cjs.js","sources":["../../src/database/mysql.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 { randomBytes } from 'node:crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport yn from 'yn';\nimport { waitForReady } from '../util/waitForReady';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForMysqlReady(\n connection: Knex.MySqlConnectionConfig,\n): Promise<void> {\n await waitForReady(async () => {\n const knex = knexFactory({\n client: 'mysql2',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n try {\n const result = await knex.select(knex.raw('version() AS version'));\n return Array.isArray(result) && Boolean(result[0]?.version);\n } finally {\n await knex.destroy();\n }\n }, 'the database');\n}\n\nexport async function startMysqlContainer(image: string): Promise<{\n connection: Knex.MySqlConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'root';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(3306)\n .withEnvironment({ MYSQL_ROOT_PASSWORD: password })\n .withTmpFs({ '/var/lib/mysql': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(3306);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForMysqlReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport function parseMysqlConnectionString(\n connectionString: string,\n): Knex.MySqlConnectionConfig {\n try {\n const {\n protocol,\n username,\n password,\n port,\n hostname,\n pathname,\n searchParams,\n } = new URL(connectionString);\n\n if (protocol !== 'mysql:') {\n throw new Error(`Unknown protocol ${protocol}`);\n } else if (!username || !password) {\n throw new Error(`Missing username/password`);\n } else if (!pathname.match(/^\\/[^/]+$/)) {\n throw new Error(`Expected single path segment`);\n }\n\n const result: Knex.MySqlConnectionConfig = {\n user: username,\n password,\n host: hostname,\n port: Number(port || 3306),\n database: decodeURIComponent(pathname.substring(1)),\n };\n\n const ssl = searchParams.get('ssl');\n if (ssl) {\n result.ssl = ssl;\n }\n\n const debug = searchParams.get('debug');\n if (debug) {\n result.debug = yn(debug);\n }\n\n return result;\n } catch (e) {\n throw new Error(`Error while parsing MySQL connection string, ${e}`, e);\n }\n}\n\nexport class MysqlEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<MysqlEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parseMysqlConnectionString(connectionString);\n return new MysqlEngine(\n properties,\n connection as Knex.MySqlConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startMysqlContainer(\n dockerImageName,\n );\n return new MysqlEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.MySqlConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.MySqlConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n const connection = {\n ...this.#connection,\n database: null as unknown as string,\n };\n return knexFactory({\n client: this.#properties.driver,\n connection,\n pool: {\n min: 0,\n max: 1,\n acquireTimeoutMillis: 20_000,\n createTimeoutMillis: 20_000,\n createRetryIntervalMillis: 1_000,\n },\n });\n }\n}\n"],"names":["waitForReady","knexFactory","uuid","yn","randomBytes","LARGER_POOL_CONFIG"],"mappings":";;;;;;;;;;;;;;AAuBA,eAAe,kBACb,UAAA,EACe;AACf,EAAA,MAAMA,0BAAa,YAAY;AAC7B,IAAA,MAAM,OAAOC,4BAAA,CAAY;AAAA,MACvB,MAAA,EAAQ,QAAA;AAAA,MACR,UAAA,EAAY;AAAA;AAAA,QAEV,GAAG;AAAA;AACL,KACD,CAAA;AACD,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAO,IAAA,CAAK,GAAA,CAAI,sBAAsB,CAAC,CAAA;AACjE,MAAA,OAAO,KAAA,CAAM,QAAQ,MAAM,CAAA,IAAK,QAAQ,MAAA,CAAO,CAAC,GAAG,OAAO,CAAA;AAAA,IAC5D,CAAA,SAAE;AACA,MAAA,MAAM,KAAK,OAAA,EAAQ;AAAA,IACrB;AAAA,EACF,GAAG,cAAc,CAAA;AACnB;AAEA,eAAsB,oBAAoB,KAAA,EAGvC;AACD,EAAA,MAAM,IAAA,GAAO,MAAA;AACb,EAAA,MAAM,WAAWC,OAAA,EAAK;AAGtB,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,IAAI,CAAA,CACrB,eAAA,CAAgB,EAAE,mBAAA,EAAqB,QAAA,EAAU,CAAA,CACjD,SAAA,CAAU,EAAE,gBAAA,EAAkB,IAAA,EAAM,CAAA,CACpC,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,IAAI,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,QAAA,EAAS;AAChD,EAAA,MAAM,gBAAgB,YAAY;AAChC,IAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,EAC1C,CAAA;AAEA,EAAA,MAAM,kBAAkB,UAAU,CAAA;AAElC,EAAA,OAAO,EAAE,YAAY,aAAA,EAAc;AACrC;AAEO,SAAS,2BACd,gBAAA,EAC4B;AAC5B,EAAA,IAAI;AACF,IAAA,MAAM;AAAA,MACJ,QAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA;AAAA,MACA,IAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF,GAAI,IAAI,GAAA,CAAI,gBAAgB,CAAA;AAE5B,IAAA,IAAI,aAAa,QAAA,EAAU;AACzB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,QAAQ,CAAA,CAAE,CAAA;AAAA,IAChD,CAAA,MAAA,IAAW,CAAC,QAAA,IAAY,CAAC,QAAA,EAAU;AACjC,MAAA,MAAM,IAAI,MAAM,CAAA,yBAAA,CAA2B,CAAA;AAAA,IAC7C,CAAA,MAAA,IAAW,CAAC,QAAA,CAAS,KAAA,CAAM,WAAW,CAAA,EAAG;AACvC,MAAA,MAAM,IAAI,MAAM,CAAA,4BAAA,CAA8B,CAAA;AAAA,IAChD;AAEA,IAAA,MAAM,MAAA,GAAqC;AAAA,MACzC,IAAA,EAAM,QAAA;AAAA,MACN,QAAA;AAAA,MACA,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,MAAA,CAAO,IAAA,IAAQ,IAAI,CAAA;AAAA,MACzB,QAAA,EAAU,kBAAA,CAAmB,QAAA,CAAS,SAAA,CAAU,CAAC,CAAC;AAAA,KACpD;AAEA,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA;AAClC,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,MAAA,CAAO,GAAA,GAAM,GAAA;AAAA,IACf;AAEA,IAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AACtC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAA,CAAO,KAAA,GAAQC,oBAAG,KAAK,CAAA;AAAA,IACzB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,CAAA,EAAG;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6CAAA,EAAgD,CAAC,IAAI,CAAC,CAAA;AAAA,EACxE;AACF;AAEO,MAAM,WAAA,CAA8B;AAAA,EACzC,aAAa,OACX,UAAA,EACsB;AACtB,IAAA,MAAM,EAAE,uCAAA,EAAyC,eAAA,EAAgB,GAC/D,UAAA;AAEF,IAAA,IAAI,uCAAA,EAAyC;AAC3C,MAAA,MAAM,gBAAA,GACJ,OAAA,CAAQ,GAAA,CAAI,uCAAuC,CAAA;AACrD,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,MAAM,UAAA,GAAa,2BAA2B,gBAAgB,CAAA;AAC9D,QAAA,OAAO,IAAI,WAAA;AAAA,UACT,UAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,MAAM,EAAE,UAAA,EAAY,aAAA,EAAc,GAAI,MAAM,mBAAA;AAAA,QAC1C;AAAA,OACF;AACA,MAAA,OAAO,IAAI,WAAA,CAAY,UAAA,EAAY,UAAA,EAAY,aAAa,CAAA;AAAA,IAC9D;AAEA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,UAAA,CAAW,IAAI,CAAA,eAAA,CAAiB,CAAA;AAAA,EACxE;AAAA,EAES,WAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EAET,WAAA,CACE,UAAA,EACA,UAAA,EACA,aAAA,EACA;AACA,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AACvB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AACvB,IAAA,IAAA,CAAK,cAAA,GAAiB,aAAA;AAAA,EACxB;AAAA,EAEA,MAAM,sBAAA,GAAwC;AAC5C,IAAA,MAAM,eAAA,GAAkB,KAAK,aAAA,EAAc;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,eAAe,CAAA,EAAA,EAAKC,uBAAA,CAAY,EAAE,CAAA,CAAE,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AAEzD,MAAA,MAAM,eAAA,CAAgB,GAAA,CAAI,oBAAA,EAAsB,CAAC,YAAY,CAAC,CAAA;AAC9D,MAAA,IAAA,CAAK,cAAA,CAAe,KAAK,YAAY,CAAA;AAErC,MAAA,MAAM,eAAeH,4BAAA,CAAY;AAAA,QAC/B,MAAA,EAAQ,KAAK,WAAA,CAAY,MAAA;AAAA,QACzB,UAAA,EAAY;AAAA,UACV,GAAG,IAAA,CAAK,WAAA;AAAA,UACR,QAAA,EAAU;AAAA,SACZ;AAAA,QACA,GAAGI;AAAA,OACJ,CAAA;AACD,MAAA,IAAA,CAAK,cAAA,CAAe,KAAK,YAAY,CAAA;AAErC,MAAA,OAAO,YAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,MAAM,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,MAAM,QAAA,GAA0B;AAC9B,IAAA,KAAA,MAAW,QAAA,IAAY,KAAK,cAAA,EAAgB;AAC1C,MAAA,MAAM,SAAS,OAAA,EAAQ;AAAA,IACzB;AAEA,IAAA,MAAM,eAAA,GAAkB,KAAK,aAAA,EAAc;AAC3C,IAAA,IAAI;AACF,MAAA,KAAA,MAAW,YAAA,IAAgB,KAAK,cAAA,EAAgB;AAC9C,QAAA,MAAM,eAAA,CAAgB,GAAA,CAAI,kBAAA,EAAoB,CAAC,YAAY,CAAC,CAAA;AAAA,MAC9D;AAAA,IACF,CAAA,SAAE;AACA,MAAA,MAAM,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAEA,IAAA,MAAM,KAAK,cAAA,IAAiB;AAAA,EAC9B;AAAA,EAEA,aAAA,GAAsB;AACpB,IAAA,MAAM,UAAA,GAAa;AAAA,MACjB,GAAG,IAAA,CAAK,WAAA;AAAA,MACR,QAAA,EAAU;AAAA,KACZ;AACA,IAAA,OAAOJ,4BAAA,CAAY;AAAA,MACjB,MAAA,EAAQ,KAAK,WAAA,CAAY,MAAA;AAAA,MACzB,UAAA;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,GAAA,EAAK,CAAA;AAAA,QACL,GAAA,EAAK,CAAA;AAAA,QACL,oBAAA,EAAsB,GAAA;AAAA,QACtB,mBAAA,EAAqB,GAAA;AAAA,QACrB,yBAAA,EAA2B;AAAA;AAC7B,KACD,CAAA;AAAA,EACH;AACF;;;;;;"}
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var errors = require('@backstage/errors');
4
3
  var node_crypto = require('node:crypto');
5
4
  var knexFactory = require('knex');
6
5
  var pgConnectionString = require('pg-connection-string');
7
6
  var uuid = require('uuid');
7
+ var waitForReady = require('../util/waitForReady.cjs.js');
8
8
  var types = require('./types.cjs.js');
9
9
 
10
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
@@ -12,36 +12,21 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
12
12
  var knexFactory__default = /*#__PURE__*/_interopDefaultCompat(knexFactory);
13
13
 
14
14
  async function waitForPostgresReady(connection) {
15
- const startTime = Date.now();
16
- let lastError;
17
- let attempts = 0;
18
- for (; ; ) {
19
- attempts += 1;
20
- let knex;
15
+ await waitForReady.waitForReady(async () => {
16
+ const knex = knexFactory__default.default({
17
+ client: "pg",
18
+ connection: {
19
+ // make a copy because the driver mutates this
20
+ ...connection
21
+ }
22
+ });
21
23
  try {
22
- knex = knexFactory__default.default({
23
- client: "pg",
24
- connection: {
25
- // make a copy because the driver mutates this
26
- ...connection
27
- }
28
- });
29
24
  const result = await knex.select(knex.raw("version()"));
30
- if (Array.isArray(result) && result[0]?.version) {
31
- return;
32
- }
33
- } catch (e) {
34
- lastError = e;
25
+ return Array.isArray(result) && Boolean(result[0]?.version);
35
26
  } finally {
36
- await knex?.destroy();
37
- }
38
- if (Date.now() - startTime > 3e4) {
39
- throw new Error(
40
- `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${lastError ? `last error was ${errors.stringifyError(lastError)}` : "(no errors thrown)"}`
41
- );
27
+ await knex.destroy();
42
28
  }
43
- await new Promise((resolve) => setTimeout(resolve, 100));
44
- }
29
+ }, "the database");
45
30
  }
46
31
  async function startPostgresContainer(image) {
47
32
  const user = "postgres";
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.cjs.js","sources":["../../src/database/postgres.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 { stringifyError } from '@backstage/errors';\nimport { randomBytes } from 'node:crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { parse as parsePgConnectionString } from 'pg-connection-string';\nimport { v4 as uuid } from 'uuid';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForPostgresReady(\n connection: Knex.PgConnectionConfig,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: Error | undefined;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n let knex: Knex | undefined;\n try {\n knex = knexFactory({\n client: 'pg',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n const result = await knex.select(knex.raw('version()'));\n if (Array.isArray(result) && result[0]?.version) {\n return;\n }\n } catch (e) {\n lastError = e;\n } finally {\n await knex?.destroy();\n }\n\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function startPostgresContainer(image: string): Promise<{\n connection: Knex.PgConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'postgres';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(5432)\n .withEnvironment({\n // Since postgres 18, the default directory changed - so we pin it here\n PGDATA: '/var/lib/postgresql/data',\n POSTGRES_PASSWORD: password,\n })\n .withTmpFs({ '/var/lib/postgresql/data': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(5432);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForPostgresReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport class PostgresEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<PostgresEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parsePgConnectionString(connectionString);\n return new PostgresEngine(\n properties,\n connection as Knex.PgConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startPostgresContainer(\n dockerImageName,\n );\n return new PostgresEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.PgConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.PgConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n return knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: 'postgres',\n },\n pool: {\n acquireTimeoutMillis: 10000,\n },\n });\n }\n}\n"],"names":["knexFactory","stringifyError","uuid","parsePgConnectionString","randomBytes","LARGER_POOL_CONFIG"],"mappings":"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"}
1
+ {"version":3,"file":"postgres.cjs.js","sources":["../../src/database/postgres.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 { randomBytes } from 'node:crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { parse as parsePgConnectionString } from 'pg-connection-string';\nimport { v4 as uuid } from 'uuid';\nimport { waitForReady } from '../util/waitForReady';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForPostgresReady(\n connection: Knex.PgConnectionConfig,\n): Promise<void> {\n await waitForReady(async () => {\n const knex = knexFactory({\n client: 'pg',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n try {\n const result = await knex.select(knex.raw('version()'));\n return Array.isArray(result) && Boolean(result[0]?.version);\n } finally {\n await knex.destroy();\n }\n }, 'the database');\n}\n\nexport async function startPostgresContainer(image: string): Promise<{\n connection: Knex.PgConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'postgres';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(5432)\n .withEnvironment({\n // Since postgres 18, the default directory changed - so we pin it here\n PGDATA: '/var/lib/postgresql/data',\n POSTGRES_PASSWORD: password,\n })\n .withTmpFs({ '/var/lib/postgresql/data': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(5432);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForPostgresReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport class PostgresEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<PostgresEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parsePgConnectionString(connectionString);\n return new PostgresEngine(\n properties,\n connection as Knex.PgConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startPostgresContainer(\n dockerImageName,\n );\n return new PostgresEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.PgConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.PgConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n return knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: 'postgres',\n },\n pool: {\n acquireTimeoutMillis: 10000,\n },\n });\n }\n}\n"],"names":["waitForReady","knexFactory","uuid","parsePgConnectionString","randomBytes","LARGER_POOL_CONFIG"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ async function waitForReady(probe, label, timeoutMs = 3e4) {
6
+ const startTime = Date.now();
7
+ let lastError;
8
+ let attempts = 0;
9
+ for (; ; ) {
10
+ attempts += 1;
11
+ try {
12
+ if (await probe()) {
13
+ return;
14
+ }
15
+ } catch (e) {
16
+ lastError = e;
17
+ }
18
+ if (Date.now() - startTime > timeoutMs) {
19
+ throw new Error(
20
+ `Timed out waiting for ${label} to be ready for connections, ${attempts} attempts, ${lastError ? `last error was ${errors.stringifyError(lastError)}` : "(no errors thrown)"}`
21
+ );
22
+ }
23
+ await new Promise((resolve) => setTimeout(resolve, 100));
24
+ }
25
+ }
26
+
27
+ exports.waitForReady = waitForReady;
28
+ //# sourceMappingURL=waitForReady.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waitForReady.cjs.js","sources":["../../src/util/waitForReady.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 { stringifyError } from '@backstage/errors';\n\n/**\n * Polls a probe function until it succeeds or the timeout is reached.\n *\n * @param probe - An async function that should return `true` when the\n * service is ready. Throwing is treated as \"not ready yet\".\n * @param label - A human-readable label used in the timeout error message.\n * @param timeoutMs - Maximum time to wait in milliseconds (default 30 000).\n */\nexport async function waitForReady(\n probe: () => Promise<boolean>,\n label: string,\n timeoutMs: number = 30_000,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: unknown;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n try {\n if (await probe()) {\n return;\n }\n } catch (e) {\n lastError = e;\n }\n\n if (Date.now() - startTime > timeoutMs) {\n throw new Error(\n `Timed out waiting for ${label} to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n"],"names":["stringifyError"],"mappings":";;;;AA0BA,eAAsB,YAAA,CACpB,KAAA,EACA,KAAA,EACA,SAAA,GAAoB,GAAA,EACL;AACf,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,WAAS;AACP,IAAA,QAAA,IAAY,CAAA;AAEZ,IAAA,IAAI;AACF,MAAA,IAAI,MAAM,OAAM,EAAG;AACjB,QAAA;AAAA,MACF;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,SAAA,GAAY,CAAA;AAAA,IACd;AAEA,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,SAAA,EAAW;AACtC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,sBAAA,EAAyB,KAAK,CAAA,8BAAA,EAAiC,QAAQ,CAAA,WAAA,EACrE,SAAA,GACI,CAAA,eAAA,EAAkBA,qBAAA,CAAe,SAAS,CAAC,CAAA,CAAA,GAC3C,oBACN,CAAA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAAA,EACvD;AACF;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-test-utils",
3
- "version": "1.11.2-next.1",
3
+ "version": "1.11.2",
4
4
  "description": "Test helpers library for Backstage backends",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -57,15 +57,15 @@
57
57
  "test": "backstage-cli package test"
58
58
  },
59
59
  "dependencies": {
60
- "@backstage/backend-app-api": "1.6.1-next.1",
61
- "@backstage/backend-defaults": "0.16.1-next.1",
62
- "@backstage/backend-plugin-api": "1.9.0-next.1",
63
- "@backstage/config": "1.3.6",
64
- "@backstage/errors": "1.2.7",
65
- "@backstage/plugin-auth-node": "0.7.0-next.1",
66
- "@backstage/plugin-events-node": "0.4.21-next.1",
67
- "@backstage/plugin-permission-common": "0.9.7",
68
- "@backstage/types": "1.2.2",
60
+ "@backstage/backend-app-api": "^1.6.1",
61
+ "@backstage/backend-defaults": "^0.17.0",
62
+ "@backstage/backend-plugin-api": "^1.9.0",
63
+ "@backstage/config": "^1.3.7",
64
+ "@backstage/errors": "^1.3.0",
65
+ "@backstage/plugin-auth-node": "^0.7.0",
66
+ "@backstage/plugin-events-node": "^0.4.21",
67
+ "@backstage/plugin-permission-common": "^0.9.8",
68
+ "@backstage/types": "^1.2.2",
69
69
  "@keyv/memcache": "^2.0.1",
70
70
  "@keyv/redis": "^4.0.1",
71
71
  "@keyv/valkey": "^1.0.1",
@@ -91,7 +91,7 @@
91
91
  "zod-to-json-schema": "^3.25.1"
92
92
  },
93
93
  "devDependencies": {
94
- "@backstage/cli": "0.36.1-next.1",
94
+ "@backstage/cli": "^0.36.1",
95
95
  "@types/jest": "*",
96
96
  "@types/lodash": "^4.14.151",
97
97
  "@types/supertest": "^2.0.8",