From 26b7da8b5c6aa7518f31c8b9eba638460c636c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Thu, 6 Jan 2022 10:31:00 +0100 Subject: [PATCH] feat: add support for cdnPrefix for static assets (#1191) --- .../__snapshots__/create-config.test.ts.snap | 1 + src/lib/app.test.ts | 14 ++-- src/lib/app.ts | 16 ++--- src/lib/create-config.ts | 1 + src/lib/middleware/oss-authentication.test.ts | 12 ++-- src/lib/routes/admin-api/config.test.ts | 8 +-- src/lib/routes/admin-api/context.test.ts | 8 +-- src/lib/routes/admin-api/email.test.ts | 16 ++--- src/lib/routes/admin-api/events.test.ts | 8 +-- src/lib/routes/admin-api/metrics.test.ts | 8 +-- src/lib/routes/admin-api/strategy.test.ts | 66 +++++++++---------- src/lib/routes/admin-api/tag.test.ts | 8 +-- src/lib/routes/admin-api/user.test.ts | 2 +- src/lib/routes/backstage.test.ts | 2 +- src/lib/routes/client-api/feature.test.ts | 8 +-- src/lib/routes/client-api/metrics.test.ts | 10 +-- src/lib/routes/client-api/register.test.ts | 8 +-- src/lib/routes/health-check.test.ts | 12 ++-- src/lib/routes/index.test.ts | 8 +-- src/lib/server-impl.ts | 2 +- src/lib/types/option.ts | 1 + src/lib/util/load-index-html.ts | 24 +++++++ src/lib/util/rewriteHTML.ts | 12 +++- src/server-dev.ts | 1 + src/test/config/test-config.ts | 4 -- src/test/e2e/helpers/test-helper.ts | 6 +- yarn.lock | 2 +- 27 files changed, 147 insertions(+), 121 deletions(-) create mode 100644 src/lib/util/load-index-html.ts diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index ca585e2f378..6ddd68a1676 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -63,6 +63,7 @@ Object { "secureHeaders": false, "server": Object { "baseUriPath": "", + "cdnPrefix": undefined, "enableRequestLogger": false, "gracefulShutdownEnable": true, "gracefulShutdownTimeout": 1000, diff --git a/src/lib/app.test.ts b/src/lib/app.test.ts index 7f98672ddf3..bf860ab2af4 100644 --- a/src/lib/app.test.ts +++ b/src/lib/app.test.ts @@ -11,32 +11,32 @@ jest.mock( }, ); -const getApp = require('./app'); +const getApp = require('./app').default; -test('should not throw when valid config', () => { +test('should not throw when valid config', async () => { const config = createTestConfig(); - const app = getApp(config, {}, {}); + const app = await getApp(config, {}, {}); expect(typeof app.listen).toBe('function'); }); -test('should call preHook', () => { +test('should call preHook', async () => { let called = 0; const config = createTestConfig({ preHook: () => { called++; }, }); - getApp(config, {}, {}); + await getApp(config, {}, {}); expect(called).toBe(1); }); -test('should call preRouterHook', () => { +test('should call preRouterHook', async () => { let called = 0; const config = createTestConfig({ preRouterHook: () => { called++; }, }); - getApp(config, {}, {}); + await getApp(config, {}, {}); expect(called).toBe(1); }); diff --git a/src/lib/app.ts b/src/lib/app.ts index 23ce3076fcc..a6d68d9ee91 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -1,5 +1,4 @@ import { publicFolder } from 'unleash-frontend'; -import fs from 'fs'; import express, { Application, RequestHandler } from 'express'; import cors from 'cors'; import compression from 'compression'; @@ -22,23 +21,19 @@ import ossAuthentication from './middleware/oss-authentication'; import noAuthentication from './middleware/no-authentication'; import secureHeaders from './middleware/secure-headers'; -import { rewriteHTML } from './util/rewriteHTML'; +import { loadIndexHTML } from './util/load-index-html'; -export default function getApp( +export default async function getApp( config: IUnleashConfig, stores: IUnleashStores, services: IUnleashServices, unleashSession?: RequestHandler, -): Application { +): Promise { const app = express(); const baseUriPath = config.server.baseUriPath || ''; - let indexHTML = fs - .readFileSync(path.join(publicFolder, 'index.html')) - .toString(); - - indexHTML = rewriteHTML(indexHTML, baseUriPath); + let indexHTML = await loadIndexHTML(config, publicFolder); app.set('trust proxy', true); app.disable('x-powered-by'); @@ -68,7 +63,7 @@ export default function getApp( app.use(secureHeaders(config)); app.use(express.urlencoded({ extended: true })); app.use(favicon(path.join(publicFolder, 'favicon.ico'))); - + app.use(baseUriPath, favicon(path.join(publicFolder, 'favicon.ico'))); app.use(baseUriPath, express.static(publicFolder, { index: false })); if (config.enableOAS) { @@ -151,4 +146,3 @@ export default function getApp( }); return app; } -module.exports = getApp; diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 3722bcb4c52..2e4ddcc203c 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -107,6 +107,7 @@ const defaultServerOption: IServerOption = { host: process.env.HTTP_HOST, port: safeNumber(process.env.HTTP_PORT || process.env.PORT, 4242), baseUriPath: formatBaseUri(process.env.BASE_URI_PATH), + cdnPrefix: process.env.CDN_PREFIX, unleashUrl: process.env.UNLEASH_URL || 'http://localhost:4242', serverMetrics: true, keepAliveTimeout: minutesToMilliseconds(1), diff --git a/src/lib/middleware/oss-authentication.test.ts b/src/lib/middleware/oss-authentication.test.ts index 22a09fb0339..162e8426724 100644 --- a/src/lib/middleware/oss-authentication.test.ts +++ b/src/lib/middleware/oss-authentication.test.ts @@ -8,7 +8,7 @@ import getApp from '../app'; import User from '../types/user'; import sessionDb from './session-db'; -function getSetup(preRouterHook) { +async function getSetup(preRouterHook) { const base = `/random${Math.round(Math.random() * 1000)}`; const config = createTestConfig({ server: { baseUriPath: base }, @@ -23,7 +23,7 @@ function getSetup(preRouterHook) { const stores = createStores(); const services = createServices(stores, config); const unleashSession = sessionDb(config, undefined); - const app = getApp(config, stores, services, unleashSession); + const app = await getApp(config, stores, services, unleashSession); return { base, @@ -31,17 +31,17 @@ function getSetup(preRouterHook) { }; } -test('should return 401 when missing user', () => { +test('should return 401 when missing user', async () => { expect.assertions(0); - const { base, request } = getSetup(() => {}); + const { base, request } = await getSetup(() => {}); return request.get(`${base}/api/protectedResource`).expect(401); }); -test('should return 200 when user exists', () => { +test('should return 200 when user exists', async () => { expect.assertions(0); const user = new User({ id: 1, email: 'some@mail.com' }); - const { base, request } = getSetup((app) => + const { base, request } = await getSetup((app) => app.use((req, res, next) => { req.user = user; next(); diff --git a/src/lib/routes/admin-api/config.test.ts b/src/lib/routes/admin-api/config.test.ts index 48ab515e9cf..1d9b4c538c4 100644 --- a/src/lib/routes/admin-api/config.test.ts +++ b/src/lib/routes/admin-api/config.test.ts @@ -10,7 +10,7 @@ const uiConfig = { slogan: 'hello', }; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const config = createTestConfig({ server: { baseUriPath: base }, @@ -19,7 +19,7 @@ function getSetup() { const stores = createStores(); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, @@ -36,8 +36,8 @@ let request; let base; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); request = setup.request; base = setup.base; destroy = setup.destroy; diff --git a/src/lib/routes/admin-api/context.test.ts b/src/lib/routes/admin-api/context.test.ts index 009cd7c0db1..bc49eeebf6b 100644 --- a/src/lib/routes/admin-api/context.test.ts +++ b/src/lib/routes/admin-api/context.test.ts @@ -5,7 +5,7 @@ import { createServices } from '../../services'; import permissions from '../../../test/fixtures/permissions'; import getApp from '../../app'; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const perms = permissions(); const config = createTestConfig({ @@ -15,7 +15,7 @@ function getSetup() { const stores = createStores(); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, @@ -32,8 +32,8 @@ let base; let request; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); base = setup.base; request = setup.request; destroy = setup.destroy; diff --git a/src/lib/routes/admin-api/email.test.ts b/src/lib/routes/admin-api/email.test.ts index 98e876da232..ae1d39ac814 100644 --- a/src/lib/routes/admin-api/email.test.ts +++ b/src/lib/routes/admin-api/email.test.ts @@ -5,7 +5,7 @@ import { createServices } from '../../services'; import permissions from '../../../test/fixtures/permissions'; import getApp from '../../app'; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = createStores(); const perms = permissions(); @@ -15,7 +15,7 @@ function getSetup() { }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, @@ -23,9 +23,9 @@ function getSetup() { }; } -test('should render html preview of template', () => { +test('should render html preview of template', async () => { expect.assertions(0); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); return request .get( `${base}/api/admin/email/preview/html/reset-password?name=Test%20Test`, @@ -35,9 +35,9 @@ test('should render html preview of template', () => { .expect((res) => 'Test Test' in res.body); }); -test('should render text preview of template', () => { +test('should render text preview of template', async () => { expect.assertions(0); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); return request .get( `${base}/api/admin/email/preview/text/reset-password?name=Test%20Test`, @@ -47,9 +47,9 @@ test('should render text preview of template', () => { .expect((res) => 'Test Test' in res.body); }); -test('Requesting a non-existing template should yield 404', () => { +test('Requesting a non-existing template should yield 404', async () => { expect.assertions(0); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); return request .get(`${base}/api/admin/email/preview/text/some-non-existing-template`) .expect(404); diff --git a/src/lib/routes/admin-api/events.test.ts b/src/lib/routes/admin-api/events.test.ts index 0d2fc8e0050..48291643720 100644 --- a/src/lib/routes/admin-api/events.test.ts +++ b/src/lib/routes/admin-api/events.test.ts @@ -6,21 +6,21 @@ import createStores from '../../../test/fixtures/store'; import getApp from '../../app'; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = createStores(); const config = createTestConfig({ server: { baseUriPath: base }, }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, eventStore: stores.eventStore, request: supertest(app) }; } -test('should get empty events list via admin', () => { +test('should get empty events list via admin', async () => { expect.assertions(1); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); return request .get(`${base}/api/admin/events`) .expect('Content-Type', /json/) diff --git a/src/lib/routes/admin-api/metrics.test.ts b/src/lib/routes/admin-api/metrics.test.ts index 1a28ce7672a..eff34c4af7d 100644 --- a/src/lib/routes/admin-api/metrics.test.ts +++ b/src/lib/routes/admin-api/metrics.test.ts @@ -5,14 +5,14 @@ import getApp from '../../app'; import { createTestConfig } from '../../../test/config/test-config'; import { createServices } from '../../services'; -function getSetup() { +async function getSetup() { const stores = createStores(); const perms = permissions(); const config = createTestConfig({ preRouterHook: perms.hook, }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { request: supertest(app), @@ -30,8 +30,8 @@ let stores; let request; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); stores = setup.stores; request = setup.request; destroy = setup.destroy; diff --git a/src/lib/routes/admin-api/strategy.test.ts b/src/lib/routes/admin-api/strategy.test.ts index cb030ae0c4b..76d5798fc39 100644 --- a/src/lib/routes/admin-api/strategy.test.ts +++ b/src/lib/routes/admin-api/strategy.test.ts @@ -7,7 +7,7 @@ import { createServices } from '../../services'; let destroy; -function getSetup() { +async function getSetup() { const randomBase = `/random${Math.round(Math.random() * 1000)}`; const perms = permissions(); const stores = createStores(); @@ -16,7 +16,7 @@ function getSetup() { preRouterHook: perms.hook, }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); destroy = () => { services.versionService.destroy(); @@ -36,8 +36,8 @@ afterEach(() => { destroy(); }); -test('add version numbers for /strategies', () => { - const { request, base } = getSetup(); +test('add version numbers for /strategies', async () => { + const { request, base } = await getSetup(); return request .get(`${base}/api/admin/strategies`) .expect('Content-Type', /json/) @@ -47,8 +47,8 @@ test('add version numbers for /strategies', () => { }); }); -test('require a name when creating a new strategy', () => { - const { request, base } = getSetup(); +test('require a name when creating a new strategy', async () => { + const { request, base } = await getSetup(); return request .post(`${base}/api/admin/strategies`) .send({}) @@ -60,8 +60,8 @@ test('require a name when creating a new strategy', () => { }); }); -test('require parameters array when creating a new stratey', () => { - const { request, base } = getSetup(); +test('require parameters array when creating a new strategy', async () => { + const { request, base } = await getSetup(); return request .post(`${base}/api/admin/strategies`) .send({ name: 'TestStrat' }) @@ -74,15 +74,15 @@ test('require parameters array when creating a new stratey', () => { }); test('create a new strategy with empty parameters', async () => { - const { request, base } = getSetup(); + const { request, base } = await getSetup(); return request .post(`${base}/api/admin/strategies`) .send({ name: 'TestStrat', parameters: [] }) .expect(201); }); -test('not be possible to override name', () => { - const { request, base, strategyStore } = getSetup(); +test('not be possible to override name', async () => { + const { request, base, strategyStore } = await getSetup(); strategyStore.createStrategy({ name: 'Testing', parameters: [] }); return request @@ -91,8 +91,8 @@ test('not be possible to override name', () => { .expect(409); }); -test('update strategy', () => { - const { request, base, strategyStore } = getSetup(); +test('update strategy', async () => { + const { request, base, strategyStore } = await getSetup(); const name = 'AnotherStrat'; strategyStore.createStrategy({ name, parameters: [] }); @@ -102,8 +102,8 @@ test('update strategy', () => { .expect(200); }); -test('not update unknown strategy', () => { - const { request, base } = getSetup(); +test('not update unknown strategy', async () => { + const { request, base } = await getSetup(); const name = 'UnknownStrat'; return request .put(`${base}/api/admin/strategies/${name}`) @@ -111,8 +111,8 @@ test('not update unknown strategy', () => { .expect(404); }); -test('validate format when updating strategy', () => { - const { request, base, strategyStore } = getSetup(); +test('validate format when updating strategy', async () => { + const { request, base, strategyStore } = await getSetup(); const name = 'AnotherStrat'; strategyStore.createStrategy({ name, parameters: [] }); @@ -122,16 +122,16 @@ test('validate format when updating strategy', () => { .expect(400); }); -test('editable=false will stop delete request', () => { +test('editable=false will stop delete request', async () => { jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn()); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); const name = 'default'; return request.delete(`${base}/api/admin/strategies/${name}`).expect(500); }); -test('editable=false will stop edit request', () => { +test('editable=false will stop edit request', async () => { jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn()); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); const name = 'default'; return request .put(`${base}/api/admin/strategies/${name}`) @@ -139,8 +139,8 @@ test('editable=false will stop edit request', () => { .expect(500); }); -test('editable=true will allow delete request', () => { - const { request, base, strategyStore } = getSetup(); +test('editable=true will allow delete request', async () => { + const { request, base, strategyStore } = await getSetup(); const name = 'deleteStrat'; strategyStore.createStrategy({ name, parameters: [] }); @@ -150,8 +150,8 @@ test('editable=true will allow delete request', () => { .expect(200); }); -test('editable=true will allow edit request', () => { - const { request, base, strategyStore } = getSetup(); +test('editable=true will allow edit request', async () => { + const { request, base, strategyStore } = await getSetup(); const name = 'editStrat'; strategyStore.createStrategy({ name, parameters: [] }); @@ -162,7 +162,7 @@ test('editable=true will allow edit request', () => { }); test('deprecating a strategy works', async () => { - const { request, base, strategyStore } = getSetup(); + const { request, base, strategyStore } = await getSetup(); const name = 'editStrat'; strategyStore.createStrategy({ name, parameters: [] }); @@ -177,8 +177,8 @@ test('deprecating a strategy works', async () => { .expect((res) => expect(res.body.deprecated).toBe(true)); }); -test('deprecating a non-existent strategy yields 404', () => { - const { request, base } = getSetup(); +test('deprecating a non-existent strategy yields 404', async () => { + const { request, base } = await getSetup(); return request .post(`${base}/api/admin/strategies/non-existent-strategy/deprecate`) .set('Content-Type', 'application/json') @@ -186,7 +186,7 @@ test('deprecating a non-existent strategy yields 404', () => { }); test('reactivating a strategy works', async () => { - const { request, base, strategyStore } = getSetup(); + const { request, base, strategyStore } = await getSetup(); const name = 'editStrat'; strategyStore.createStrategy({ name, parameters: [] }); @@ -201,16 +201,16 @@ test('reactivating a strategy works', async () => { .expect((res) => expect(res.body.deprecated).toBe(false)); }); -test('reactivating a non-existent strategy yields 404', () => { - const { request, base } = getSetup(); +test('reactivating a non-existent strategy yields 404', async () => { + const { request, base } = await getSetup(); return request .post(`${base}/api/admin/strategies/non-existent-strategy/reactivate`) .set('Content-Type', 'application/json') .expect(404); }); -test("deprecating 'default' strategy will yield 403", () => { +test("deprecating 'default' strategy will yield 403", async () => { jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn()); - const { request, base } = getSetup(); + const { request, base } = await getSetup(); return request .post(`${base}/api/admin/strategies/default/deprecate`) .set('Content-Type', 'application/json') diff --git a/src/lib/routes/admin-api/tag.test.ts b/src/lib/routes/admin-api/tag.test.ts index cad68ea9498..5fbcf177398 100644 --- a/src/lib/routes/admin-api/tag.test.ts +++ b/src/lib/routes/admin-api/tag.test.ts @@ -5,7 +5,7 @@ import getApp from '../../app'; import { createTestConfig } from '../../../test/config/test-config'; import { createServices } from '../../services'; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = createStores(); const perms = permissions(); @@ -14,7 +14,7 @@ function getSetup() { preRouterHook: perms.hook, }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, @@ -34,8 +34,8 @@ let tagStore; let request; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); base = setup.base; tagStore = setup.tagStore; request = setup.request; diff --git a/src/lib/routes/admin-api/user.test.ts b/src/lib/routes/admin-api/user.test.ts index 29225509bfc..5de696a741c 100644 --- a/src/lib/routes/admin-api/user.test.ts +++ b/src/lib/routes/admin-api/user.test.ts @@ -23,7 +23,7 @@ async function getSetup() { server: { baseUriPath: base }, }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, userStore: stores.userStore, diff --git a/src/lib/routes/backstage.test.ts b/src/lib/routes/backstage.test.ts index b1ad4d0d176..858b1b91184 100644 --- a/src/lib/routes/backstage.test.ts +++ b/src/lib/routes/backstage.test.ts @@ -11,7 +11,7 @@ test('should enable prometheus', async () => { const config = createTestConfig(); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); const request = supertest(app); diff --git a/src/lib/routes/client-api/feature.test.ts b/src/lib/routes/client-api/feature.test.ts index afe72aed368..728cff5fbed 100644 --- a/src/lib/routes/client-api/feature.test.ts +++ b/src/lib/routes/client-api/feature.test.ts @@ -7,7 +7,7 @@ import FeatureController from './feature'; import { createTestConfig } from '../../../test/config/test-config'; import { secondsToMilliseconds } from 'date-fns'; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = createStores(); const config = createTestConfig({ @@ -15,7 +15,7 @@ function getSetup() { }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, @@ -35,8 +35,8 @@ let request; let destroy; let featureToggleClientStore; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); base = setup.base; request = setup.request; featureToggleClientStore = setup.featureToggleClientStore; diff --git a/src/lib/routes/client-api/metrics.test.ts b/src/lib/routes/client-api/metrics.test.ts index 98dc6372f47..ea8b7a55ed9 100644 --- a/src/lib/routes/client-api/metrics.test.ts +++ b/src/lib/routes/client-api/metrics.test.ts @@ -7,12 +7,12 @@ import { createServices } from '../../services'; import { IUnleashStores } from '../../types'; import { IUnleashOptions } from '../../server-impl'; -function getSetup(opts?: IUnleashOptions) { +async function getSetup(opts?: IUnleashOptions) { const stores = createStores(); const config = createTestConfig(opts); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { request: supertest(app), @@ -29,8 +29,8 @@ let request; let stores: IUnleashStores; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); request = setup.request; stores = setup.stores; destroy = setup.destroy; @@ -83,7 +83,7 @@ test('should accept client metrics with yes/no', () => { }); test('should accept client metrics with yes/no with metricsV2', async () => { - const testRunner = getSetup({ + const testRunner = await getSetup({ experimental: { metricsV2: { enabled: true } }, }); await testRunner.request diff --git a/src/lib/routes/client-api/register.test.ts b/src/lib/routes/client-api/register.test.ts index 17b349d870e..f6e67549778 100644 --- a/src/lib/routes/client-api/register.test.ts +++ b/src/lib/routes/client-api/register.test.ts @@ -5,11 +5,11 @@ import getLogger from '../../../test/fixtures/no-logger'; import getApp from '../../app'; import { createServices } from '../../services'; -function getSetup() { +async function getSetup() { const stores = createStores(); const config = createTestConfig(); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { request: supertest(app), @@ -23,8 +23,8 @@ function getSetup() { } let request; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); request = setup.request; destroy = setup.destroy; }); diff --git a/src/lib/routes/health-check.test.ts b/src/lib/routes/health-check.test.ts index 42334e09cfe..93a3c44a214 100644 --- a/src/lib/routes/health-check.test.ts +++ b/src/lib/routes/health-check.test.ts @@ -7,11 +7,11 @@ import getLogger from '../../test/fixtures/no-logger'; import getApp from '../app'; import { IUnleashStores } from '../types'; -function getSetup() { +async function getSetup() { const stores = createStores(); const config = createTestConfig(); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { request: supertest(app), @@ -26,8 +26,8 @@ function getSetup() { let request; let destroy; let stores; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); request = setup.request; destroy = setup.destroy; stores = setup.stores; @@ -38,7 +38,7 @@ afterEach(() => { getLogger.setMuteError(false); }); -test('should give 500 when db is failing', () => { +test('should give 500 when db is failing', async () => { jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn()); const config = createTestConfig(); const failingStores: Partial = { @@ -54,7 +54,7 @@ test('should give 500 when db is failing', () => { // @ts-ignore const services = createServices(failingStores, config); // @ts-ignore - const app = getApp(createTestConfig(), failingStores, services); + const app = await getApp(createTestConfig(), failingStores, services); request = supertest(app); getLogger.setMuteError(true); expect.assertions(2); diff --git a/src/lib/routes/index.test.ts b/src/lib/routes/index.test.ts index d0ed05bdad0..dd68c1a014d 100644 --- a/src/lib/routes/index.test.ts +++ b/src/lib/routes/index.test.ts @@ -4,14 +4,14 @@ import createStores from '../../test/fixtures/store'; import getApp from '../app'; import { createServices } from '../services'; -function getSetup() { +async function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = createStores(); const config = createTestConfig({ server: { baseUriPath: base }, }); const services = createServices(stores, config); - const app = getApp(config, stores, services); + const app = await getApp(config, stores, services); return { base, @@ -27,8 +27,8 @@ function getSetup() { let base; let request; let destroy; -beforeEach(() => { - const setup = getSetup(); +beforeEach(async () => { + const setup = await getSetup(); base = setup.base; request = setup.request; destroy = setup.destroy; diff --git a/src/lib/server-impl.ts b/src/lib/server-impl.ts index bc983276507..af038507a46 100644 --- a/src/lib/server-impl.ts +++ b/src/lib/server-impl.ts @@ -57,7 +57,7 @@ async function createApp( // eslint-disable-next-line no-param-reassign config.server.secret = secret; } - const app = getApp(config, stores, services, unleashSession); + const app = await getApp(config, stores, services, unleashSession); if (typeof config.eventHook === 'function') { addEventHook(config.eventHook, stores.eventStore); diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index a9f1676a935..d0e60e24c42 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -70,6 +70,7 @@ export interface IServerOption { keepAliveTimeout: number; headersTimeout: number; baseUriPath: string; + cdnPrefix?: string; unleashUrl: string; serverMetrics: boolean; enableRequestLogger: boolean; diff --git a/src/lib/util/load-index-html.ts b/src/lib/util/load-index-html.ts new file mode 100644 index 00000000000..366df14c44d --- /dev/null +++ b/src/lib/util/load-index-html.ts @@ -0,0 +1,24 @@ +import fs from 'fs'; +import { IUnleashConfig } from '../server-impl'; +import { rewriteHTML } from './rewriteHTML'; +import path from 'path'; +import fetch from 'node-fetch'; + +export async function loadIndexHTML( + config: IUnleashConfig, + publicFolder: string, +): Promise { + const { cdnPrefix, baseUriPath = '' } = config.server; + + let indexHTML: string; + if (cdnPrefix) { + const res = await fetch(`${cdnPrefix}/index.html`); + indexHTML = await res.text(); + } else { + indexHTML = fs + .readFileSync(path.join(publicFolder, 'index.html')) + .toString(); + } + + return rewriteHTML(indexHTML, baseUriPath, cdnPrefix); +} diff --git a/src/lib/util/rewriteHTML.ts b/src/lib/util/rewriteHTML.ts index 641c71ac645..a6bdf3f0f4f 100644 --- a/src/lib/util/rewriteHTML.ts +++ b/src/lib/util/rewriteHTML.ts @@ -1,7 +1,15 @@ -export const rewriteHTML = (input: string, rewriteValue: string): string => { +export const rewriteHTML = ( + input: string, + rewriteValue: string, + cdnPrefix?: string, +): string => { let result = input; result = result.replace(/::baseUriPath::/gi, rewriteValue); - result = result.replace(/\/static/gi, `${rewriteValue}/static`); + result = result.replace(/::cdnPrefix::/gi, cdnPrefix || ''); + result = result.replace( + /\/static/gi, + `${cdnPrefix || rewriteValue}/static`, + ); return result; }; diff --git a/src/server-dev.ts b/src/server-dev.ts index 67f5e1666f0..2d29a49676c 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -20,6 +20,7 @@ process.nextTick(async () => { baseUriPath: '', // keepAliveTimeout: 1, gracefulShutdownEnable: true, + // cdnPrefix: 'https://cdn.getunleash.io/unleash/v4.4.1', }, logLevel: LogLevel.debug, enableOAS: true, diff --git a/src/test/config/test-config.ts b/src/test/config/test-config.ts index 6410b87452f..864872a5432 100644 --- a/src/test/config/test-config.ts +++ b/src/test/config/test-config.ts @@ -25,7 +25,3 @@ export function createTestConfig(config?: IUnleashOptions): IUnleashConfig { const options = mergeAll([testConfig, config]); return createConfig(options); } - -module.exports = { - createTestConfig, -}; diff --git a/src/test/e2e/helpers/test-helper.ts b/src/test/e2e/helpers/test-helper.ts index 38de03a8282..229c171a224 100644 --- a/src/test/e2e/helpers/test-helper.ts +++ b/src/test/e2e/helpers/test-helper.ts @@ -18,12 +18,12 @@ export interface IUnleashTest { services: IUnleashServices; } -function createApp( +async function createApp( stores, adminAuthentication = IAuthType.NONE, preHook?: Function, customOptions?: any, -): IUnleashTest { +): Promise { const config = createTestConfig({ authentication: { type: adminAuthentication, @@ -38,7 +38,7 @@ function createApp( const unleashSession = sessionDb(config, undefined); const emitter = new EventEmitter(); emitter.setMaxListeners(0); - const app = getApp(config, stores, services, unleashSession); + const app = await getApp(config, stores, services, unleashSession); const request = supertest.agent(app); const destroy = async () => { diff --git a/yarn.lock b/yarn.lock index d693712f3da..607850be2e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7246,7 +7246,7 @@ universalify@^0.1.0, universalify@^0.1.2: resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unleash-frontend@4.4.1: +unleash-frontend@v4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-4.4.1.tgz#753008e8a1a25b204edf23595261635f030b8590" integrity sha512-hyVd56nbWkFdyEeCeHMVZjKlQyWu82QqzGT6IDKzYJruYpIk00/9dm7zycziZIwzu7GXfKkI4J6fnm6Ge7mB5g==