mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-19 01:17:18 +02:00
feat: add support for cdnPrefix for static assets (#1191)
This commit is contained in:
parent
2b59a4219a
commit
26b7da8b5c
@ -63,6 +63,7 @@ Object {
|
|||||||
"secureHeaders": false,
|
"secureHeaders": false,
|
||||||
"server": Object {
|
"server": Object {
|
||||||
"baseUriPath": "",
|
"baseUriPath": "",
|
||||||
|
"cdnPrefix": undefined,
|
||||||
"enableRequestLogger": false,
|
"enableRequestLogger": false,
|
||||||
"gracefulShutdownEnable": true,
|
"gracefulShutdownEnable": true,
|
||||||
"gracefulShutdownTimeout": 1000,
|
"gracefulShutdownTimeout": 1000,
|
||||||
|
@ -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 config = createTestConfig();
|
||||||
const app = getApp(config, {}, {});
|
const app = await getApp(config, {}, {});
|
||||||
expect(typeof app.listen).toBe('function');
|
expect(typeof app.listen).toBe('function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call preHook', () => {
|
test('should call preHook', async () => {
|
||||||
let called = 0;
|
let called = 0;
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
preHook: () => {
|
preHook: () => {
|
||||||
called++;
|
called++;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
getApp(config, {}, {});
|
await getApp(config, {}, {});
|
||||||
expect(called).toBe(1);
|
expect(called).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call preRouterHook', () => {
|
test('should call preRouterHook', async () => {
|
||||||
let called = 0;
|
let called = 0;
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
preRouterHook: () => {
|
preRouterHook: () => {
|
||||||
called++;
|
called++;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
getApp(config, {}, {});
|
await getApp(config, {}, {});
|
||||||
expect(called).toBe(1);
|
expect(called).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { publicFolder } from 'unleash-frontend';
|
import { publicFolder } from 'unleash-frontend';
|
||||||
import fs from 'fs';
|
|
||||||
import express, { Application, RequestHandler } from 'express';
|
import express, { Application, RequestHandler } from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import compression from 'compression';
|
import compression from 'compression';
|
||||||
@ -22,23 +21,19 @@ import ossAuthentication from './middleware/oss-authentication';
|
|||||||
import noAuthentication from './middleware/no-authentication';
|
import noAuthentication from './middleware/no-authentication';
|
||||||
import secureHeaders from './middleware/secure-headers';
|
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,
|
config: IUnleashConfig,
|
||||||
stores: IUnleashStores,
|
stores: IUnleashStores,
|
||||||
services: IUnleashServices,
|
services: IUnleashServices,
|
||||||
unleashSession?: RequestHandler,
|
unleashSession?: RequestHandler,
|
||||||
): Application {
|
): Promise<Application> {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const baseUriPath = config.server.baseUriPath || '';
|
const baseUriPath = config.server.baseUriPath || '';
|
||||||
|
|
||||||
let indexHTML = fs
|
let indexHTML = await loadIndexHTML(config, publicFolder);
|
||||||
.readFileSync(path.join(publicFolder, 'index.html'))
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
indexHTML = rewriteHTML(indexHTML, baseUriPath);
|
|
||||||
|
|
||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
@ -68,7 +63,7 @@ export default function getApp(
|
|||||||
app.use(secureHeaders(config));
|
app.use(secureHeaders(config));
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(favicon(path.join(publicFolder, 'favicon.ico')));
|
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 }));
|
app.use(baseUriPath, express.static(publicFolder, { index: false }));
|
||||||
|
|
||||||
if (config.enableOAS) {
|
if (config.enableOAS) {
|
||||||
@ -151,4 +146,3 @@ export default function getApp(
|
|||||||
});
|
});
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
module.exports = getApp;
|
|
||||||
|
@ -107,6 +107,7 @@ const defaultServerOption: IServerOption = {
|
|||||||
host: process.env.HTTP_HOST,
|
host: process.env.HTTP_HOST,
|
||||||
port: safeNumber(process.env.HTTP_PORT || process.env.PORT, 4242),
|
port: safeNumber(process.env.HTTP_PORT || process.env.PORT, 4242),
|
||||||
baseUriPath: formatBaseUri(process.env.BASE_URI_PATH),
|
baseUriPath: formatBaseUri(process.env.BASE_URI_PATH),
|
||||||
|
cdnPrefix: process.env.CDN_PREFIX,
|
||||||
unleashUrl: process.env.UNLEASH_URL || 'http://localhost:4242',
|
unleashUrl: process.env.UNLEASH_URL || 'http://localhost:4242',
|
||||||
serverMetrics: true,
|
serverMetrics: true,
|
||||||
keepAliveTimeout: minutesToMilliseconds(1),
|
keepAliveTimeout: minutesToMilliseconds(1),
|
||||||
|
@ -8,7 +8,7 @@ import getApp from '../app';
|
|||||||
import User from '../types/user';
|
import User from '../types/user';
|
||||||
import sessionDb from './session-db';
|
import sessionDb from './session-db';
|
||||||
|
|
||||||
function getSetup(preRouterHook) {
|
async function getSetup(preRouterHook) {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
server: { baseUriPath: base },
|
server: { baseUriPath: base },
|
||||||
@ -23,7 +23,7 @@ function getSetup(preRouterHook) {
|
|||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const unleashSession = sessionDb(config, undefined);
|
const unleashSession = sessionDb(config, undefined);
|
||||||
const app = getApp(config, stores, services, unleashSession);
|
const app = await getApp(config, stores, services, unleashSession);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
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);
|
expect.assertions(0);
|
||||||
const { base, request } = getSetup(() => {});
|
const { base, request } = await getSetup(() => {});
|
||||||
|
|
||||||
return request.get(`${base}/api/protectedResource`).expect(401);
|
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);
|
expect.assertions(0);
|
||||||
const user = new User({ id: 1, email: 'some@mail.com' });
|
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) => {
|
app.use((req, res, next) => {
|
||||||
req.user = user;
|
req.user = user;
|
||||||
next();
|
next();
|
||||||
|
@ -10,7 +10,7 @@ const uiConfig = {
|
|||||||
slogan: 'hello',
|
slogan: 'hello',
|
||||||
};
|
};
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
server: { baseUriPath: base },
|
server: { baseUriPath: base },
|
||||||
@ -19,7 +19,7 @@ function getSetup() {
|
|||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
|
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
@ -36,8 +36,8 @@ let request;
|
|||||||
let base;
|
let base;
|
||||||
let destroy;
|
let destroy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
base = setup.base;
|
base = setup.base;
|
||||||
destroy = setup.destroy;
|
destroy = setup.destroy;
|
||||||
|
@ -5,7 +5,7 @@ import { createServices } from '../../services';
|
|||||||
import permissions from '../../../test/fixtures/permissions';
|
import permissions from '../../../test/fixtures/permissions';
|
||||||
import getApp from '../../app';
|
import getApp from '../../app';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const perms = permissions();
|
const perms = permissions();
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
@ -15,7 +15,7 @@ function getSetup() {
|
|||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
|
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
@ -32,8 +32,8 @@ let base;
|
|||||||
let request;
|
let request;
|
||||||
let destroy;
|
let destroy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
base = setup.base;
|
base = setup.base;
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
destroy = setup.destroy;
|
destroy = setup.destroy;
|
||||||
|
@ -5,7 +5,7 @@ import { createServices } from '../../services';
|
|||||||
import permissions from '../../../test/fixtures/permissions';
|
import permissions from '../../../test/fixtures/permissions';
|
||||||
import getApp from '../../app';
|
import getApp from '../../app';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const perms = permissions();
|
const perms = permissions();
|
||||||
@ -15,7 +15,7 @@ function getSetup() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
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);
|
expect.assertions(0);
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.get(
|
.get(
|
||||||
`${base}/api/admin/email/preview/html/reset-password?name=Test%20Test`,
|
`${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);
|
.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);
|
expect.assertions(0);
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.get(
|
.get(
|
||||||
`${base}/api/admin/email/preview/text/reset-password?name=Test%20Test`,
|
`${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);
|
.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);
|
expect.assertions(0);
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.get(`${base}/api/admin/email/preview/text/some-non-existing-template`)
|
.get(`${base}/api/admin/email/preview/text/some-non-existing-template`)
|
||||||
.expect(404);
|
.expect(404);
|
||||||
|
@ -6,21 +6,21 @@ import createStores from '../../../test/fixtures/store';
|
|||||||
|
|
||||||
import getApp from '../../app';
|
import getApp from '../../app';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
server: { baseUriPath: base },
|
server: { baseUriPath: base },
|
||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
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) };
|
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);
|
expect.assertions(1);
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.get(`${base}/api/admin/events`)
|
.get(`${base}/api/admin/events`)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
@ -5,14 +5,14 @@ import getApp from '../../app';
|
|||||||
import { createTestConfig } from '../../../test/config/test-config';
|
import { createTestConfig } from '../../../test/config/test-config';
|
||||||
import { createServices } from '../../services';
|
import { createServices } from '../../services';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const perms = permissions();
|
const perms = permissions();
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
preRouterHook: perms.hook,
|
preRouterHook: perms.hook,
|
||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request: supertest(app),
|
request: supertest(app),
|
||||||
@ -30,8 +30,8 @@ let stores;
|
|||||||
let request;
|
let request;
|
||||||
let destroy;
|
let destroy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
stores = setup.stores;
|
stores = setup.stores;
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
destroy = setup.destroy;
|
destroy = setup.destroy;
|
||||||
|
@ -7,7 +7,7 @@ import { createServices } from '../../services';
|
|||||||
|
|
||||||
let destroy;
|
let destroy;
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const randomBase = `/random${Math.round(Math.random() * 1000)}`;
|
const randomBase = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const perms = permissions();
|
const perms = permissions();
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
@ -16,7 +16,7 @@ function getSetup() {
|
|||||||
preRouterHook: perms.hook,
|
preRouterHook: perms.hook,
|
||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
destroy = () => {
|
destroy = () => {
|
||||||
services.versionService.destroy();
|
services.versionService.destroy();
|
||||||
@ -36,8 +36,8 @@ afterEach(() => {
|
|||||||
destroy();
|
destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('add version numbers for /strategies', () => {
|
test('add version numbers for /strategies', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.get(`${base}/api/admin/strategies`)
|
.get(`${base}/api/admin/strategies`)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
@ -47,8 +47,8 @@ test('add version numbers for /strategies', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('require a name when creating a new strategy', () => {
|
test('require a name when creating a new strategy', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.post(`${base}/api/admin/strategies`)
|
.post(`${base}/api/admin/strategies`)
|
||||||
.send({})
|
.send({})
|
||||||
@ -60,8 +60,8 @@ test('require a name when creating a new strategy', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('require parameters array when creating a new stratey', () => {
|
test('require parameters array when creating a new strategy', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.post(`${base}/api/admin/strategies`)
|
.post(`${base}/api/admin/strategies`)
|
||||||
.send({ name: 'TestStrat' })
|
.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 () => {
|
test('create a new strategy with empty parameters', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.post(`${base}/api/admin/strategies`)
|
.post(`${base}/api/admin/strategies`)
|
||||||
.send({ name: 'TestStrat', parameters: [] })
|
.send({ name: 'TestStrat', parameters: [] })
|
||||||
.expect(201);
|
.expect(201);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('not be possible to override name', () => {
|
test('not be possible to override name', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
strategyStore.createStrategy({ name: 'Testing', parameters: [] });
|
strategyStore.createStrategy({ name: 'Testing', parameters: [] });
|
||||||
|
|
||||||
return request
|
return request
|
||||||
@ -91,8 +91,8 @@ test('not be possible to override name', () => {
|
|||||||
.expect(409);
|
.expect(409);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('update strategy', () => {
|
test('update strategy', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
const name = 'AnotherStrat';
|
const name = 'AnotherStrat';
|
||||||
strategyStore.createStrategy({ name, parameters: [] });
|
strategyStore.createStrategy({ name, parameters: [] });
|
||||||
|
|
||||||
@ -102,8 +102,8 @@ test('update strategy', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('not update unknown strategy', () => {
|
test('not update unknown strategy', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
const name = 'UnknownStrat';
|
const name = 'UnknownStrat';
|
||||||
return request
|
return request
|
||||||
.put(`${base}/api/admin/strategies/${name}`)
|
.put(`${base}/api/admin/strategies/${name}`)
|
||||||
@ -111,8 +111,8 @@ test('not update unknown strategy', () => {
|
|||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validate format when updating strategy', () => {
|
test('validate format when updating strategy', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
const name = 'AnotherStrat';
|
const name = 'AnotherStrat';
|
||||||
strategyStore.createStrategy({ name, parameters: [] });
|
strategyStore.createStrategy({ name, parameters: [] });
|
||||||
|
|
||||||
@ -122,16 +122,16 @@ test('validate format when updating strategy', () => {
|
|||||||
.expect(400);
|
.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());
|
jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn());
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
const name = 'default';
|
const name = 'default';
|
||||||
return request.delete(`${base}/api/admin/strategies/${name}`).expect(500);
|
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());
|
jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn());
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
const name = 'default';
|
const name = 'default';
|
||||||
return request
|
return request
|
||||||
.put(`${base}/api/admin/strategies/${name}`)
|
.put(`${base}/api/admin/strategies/${name}`)
|
||||||
@ -139,8 +139,8 @@ test('editable=false will stop edit request', () => {
|
|||||||
.expect(500);
|
.expect(500);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('editable=true will allow delete request', () => {
|
test('editable=true will allow delete request', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
const name = 'deleteStrat';
|
const name = 'deleteStrat';
|
||||||
strategyStore.createStrategy({ name, parameters: [] });
|
strategyStore.createStrategy({ name, parameters: [] });
|
||||||
|
|
||||||
@ -150,8 +150,8 @@ test('editable=true will allow delete request', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('editable=true will allow edit request', () => {
|
test('editable=true will allow edit request', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
const name = 'editStrat';
|
const name = 'editStrat';
|
||||||
strategyStore.createStrategy({ name, parameters: [] });
|
strategyStore.createStrategy({ name, parameters: [] });
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ test('editable=true will allow edit request', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('deprecating a strategy works', async () => {
|
test('deprecating a strategy works', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
const name = 'editStrat';
|
const name = 'editStrat';
|
||||||
strategyStore.createStrategy({ name, parameters: [] });
|
strategyStore.createStrategy({ name, parameters: [] });
|
||||||
|
|
||||||
@ -177,8 +177,8 @@ test('deprecating a strategy works', async () => {
|
|||||||
.expect((res) => expect(res.body.deprecated).toBe(true));
|
.expect((res) => expect(res.body.deprecated).toBe(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deprecating a non-existent strategy yields 404', () => {
|
test('deprecating a non-existent strategy yields 404', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.post(`${base}/api/admin/strategies/non-existent-strategy/deprecate`)
|
.post(`${base}/api/admin/strategies/non-existent-strategy/deprecate`)
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
@ -186,7 +186,7 @@ test('deprecating a non-existent strategy yields 404', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('reactivating a strategy works', async () => {
|
test('reactivating a strategy works', async () => {
|
||||||
const { request, base, strategyStore } = getSetup();
|
const { request, base, strategyStore } = await getSetup();
|
||||||
const name = 'editStrat';
|
const name = 'editStrat';
|
||||||
strategyStore.createStrategy({ name, parameters: [] });
|
strategyStore.createStrategy({ name, parameters: [] });
|
||||||
|
|
||||||
@ -201,16 +201,16 @@ test('reactivating a strategy works', async () => {
|
|||||||
.expect((res) => expect(res.body.deprecated).toBe(false));
|
.expect((res) => expect(res.body.deprecated).toBe(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reactivating a non-existent strategy yields 404', () => {
|
test('reactivating a non-existent strategy yields 404', async () => {
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.post(`${base}/api/admin/strategies/non-existent-strategy/reactivate`)
|
.post(`${base}/api/admin/strategies/non-existent-strategy/reactivate`)
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
.expect(404);
|
.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());
|
jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn());
|
||||||
const { request, base } = getSetup();
|
const { request, base } = await getSetup();
|
||||||
return request
|
return request
|
||||||
.post(`${base}/api/admin/strategies/default/deprecate`)
|
.post(`${base}/api/admin/strategies/default/deprecate`)
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
|
@ -5,7 +5,7 @@ import getApp from '../../app';
|
|||||||
import { createTestConfig } from '../../../test/config/test-config';
|
import { createTestConfig } from '../../../test/config/test-config';
|
||||||
import { createServices } from '../../services';
|
import { createServices } from '../../services';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const perms = permissions();
|
const perms = permissions();
|
||||||
@ -14,7 +14,7 @@ function getSetup() {
|
|||||||
preRouterHook: perms.hook,
|
preRouterHook: perms.hook,
|
||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
@ -34,8 +34,8 @@ let tagStore;
|
|||||||
let request;
|
let request;
|
||||||
let destroy;
|
let destroy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
base = setup.base;
|
base = setup.base;
|
||||||
tagStore = setup.tagStore;
|
tagStore = setup.tagStore;
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
|
@ -23,7 +23,7 @@ async function getSetup() {
|
|||||||
server: { baseUriPath: base },
|
server: { baseUriPath: base },
|
||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
userStore: stores.userStore,
|
userStore: stores.userStore,
|
||||||
|
@ -11,7 +11,7 @@ test('should enable prometheus', async () => {
|
|||||||
const config = createTestConfig();
|
const config = createTestConfig();
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
|
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
const request = supertest(app);
|
const request = supertest(app);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import FeatureController from './feature';
|
|||||||
import { createTestConfig } from '../../../test/config/test-config';
|
import { createTestConfig } from '../../../test/config/test-config';
|
||||||
import { secondsToMilliseconds } from 'date-fns';
|
import { secondsToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
@ -15,7 +15,7 @@ function getSetup() {
|
|||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
|
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
@ -35,8 +35,8 @@ let request;
|
|||||||
let destroy;
|
let destroy;
|
||||||
let featureToggleClientStore;
|
let featureToggleClientStore;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
base = setup.base;
|
base = setup.base;
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
featureToggleClientStore = setup.featureToggleClientStore;
|
featureToggleClientStore = setup.featureToggleClientStore;
|
||||||
|
@ -7,12 +7,12 @@ import { createServices } from '../../services';
|
|||||||
import { IUnleashStores } from '../../types';
|
import { IUnleashStores } from '../../types';
|
||||||
import { IUnleashOptions } from '../../server-impl';
|
import { IUnleashOptions } from '../../server-impl';
|
||||||
|
|
||||||
function getSetup(opts?: IUnleashOptions) {
|
async function getSetup(opts?: IUnleashOptions) {
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
|
|
||||||
const config = createTestConfig(opts);
|
const config = createTestConfig(opts);
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request: supertest(app),
|
request: supertest(app),
|
||||||
@ -29,8 +29,8 @@ let request;
|
|||||||
let stores: IUnleashStores;
|
let stores: IUnleashStores;
|
||||||
let destroy;
|
let destroy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
stores = setup.stores;
|
stores = setup.stores;
|
||||||
destroy = setup.destroy;
|
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 () => {
|
test('should accept client metrics with yes/no with metricsV2', async () => {
|
||||||
const testRunner = getSetup({
|
const testRunner = await getSetup({
|
||||||
experimental: { metricsV2: { enabled: true } },
|
experimental: { metricsV2: { enabled: true } },
|
||||||
});
|
});
|
||||||
await testRunner.request
|
await testRunner.request
|
||||||
|
@ -5,11 +5,11 @@ import getLogger from '../../../test/fixtures/no-logger';
|
|||||||
import getApp from '../../app';
|
import getApp from '../../app';
|
||||||
import { createServices } from '../../services';
|
import { createServices } from '../../services';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const config = createTestConfig();
|
const config = createTestConfig();
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request: supertest(app),
|
request: supertest(app),
|
||||||
@ -23,8 +23,8 @@ function getSetup() {
|
|||||||
}
|
}
|
||||||
let request;
|
let request;
|
||||||
let destroy;
|
let destroy;
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
destroy = setup.destroy;
|
destroy = setup.destroy;
|
||||||
});
|
});
|
||||||
|
@ -7,11 +7,11 @@ import getLogger from '../../test/fixtures/no-logger';
|
|||||||
import getApp from '../app';
|
import getApp from '../app';
|
||||||
import { IUnleashStores } from '../types';
|
import { IUnleashStores } from '../types';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const config = createTestConfig();
|
const config = createTestConfig();
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request: supertest(app),
|
request: supertest(app),
|
||||||
@ -26,8 +26,8 @@ function getSetup() {
|
|||||||
let request;
|
let request;
|
||||||
let destroy;
|
let destroy;
|
||||||
let stores;
|
let stores;
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
destroy = setup.destroy;
|
destroy = setup.destroy;
|
||||||
stores = setup.stores;
|
stores = setup.stores;
|
||||||
@ -38,7 +38,7 @@ afterEach(() => {
|
|||||||
getLogger.setMuteError(false);
|
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());
|
jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn());
|
||||||
const config = createTestConfig();
|
const config = createTestConfig();
|
||||||
const failingStores: Partial<IUnleashStores> = {
|
const failingStores: Partial<IUnleashStores> = {
|
||||||
@ -54,7 +54,7 @@ test('should give 500 when db is failing', () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const services = createServices(failingStores, config);
|
const services = createServices(failingStores, config);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const app = getApp(createTestConfig(), failingStores, services);
|
const app = await getApp(createTestConfig(), failingStores, services);
|
||||||
request = supertest(app);
|
request = supertest(app);
|
||||||
getLogger.setMuteError(true);
|
getLogger.setMuteError(true);
|
||||||
expect.assertions(2);
|
expect.assertions(2);
|
||||||
|
@ -4,14 +4,14 @@ import createStores from '../../test/fixtures/store';
|
|||||||
import getApp from '../app';
|
import getApp from '../app';
|
||||||
import { createServices } from '../services';
|
import { createServices } from '../services';
|
||||||
|
|
||||||
function getSetup() {
|
async function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const stores = createStores();
|
const stores = createStores();
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
server: { baseUriPath: base },
|
server: { baseUriPath: base },
|
||||||
});
|
});
|
||||||
const services = createServices(stores, config);
|
const services = createServices(stores, config);
|
||||||
const app = getApp(config, stores, services);
|
const app = await getApp(config, stores, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
@ -27,8 +27,8 @@ function getSetup() {
|
|||||||
let base;
|
let base;
|
||||||
let request;
|
let request;
|
||||||
let destroy;
|
let destroy;
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const setup = getSetup();
|
const setup = await getSetup();
|
||||||
base = setup.base;
|
base = setup.base;
|
||||||
request = setup.request;
|
request = setup.request;
|
||||||
destroy = setup.destroy;
|
destroy = setup.destroy;
|
||||||
|
@ -57,7 +57,7 @@ async function createApp(
|
|||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
config.server.secret = secret;
|
config.server.secret = secret;
|
||||||
}
|
}
|
||||||
const app = getApp(config, stores, services, unleashSession);
|
const app = await getApp(config, stores, services, unleashSession);
|
||||||
|
|
||||||
if (typeof config.eventHook === 'function') {
|
if (typeof config.eventHook === 'function') {
|
||||||
addEventHook(config.eventHook, stores.eventStore);
|
addEventHook(config.eventHook, stores.eventStore);
|
||||||
|
@ -70,6 +70,7 @@ export interface IServerOption {
|
|||||||
keepAliveTimeout: number;
|
keepAliveTimeout: number;
|
||||||
headersTimeout: number;
|
headersTimeout: number;
|
||||||
baseUriPath: string;
|
baseUriPath: string;
|
||||||
|
cdnPrefix?: string;
|
||||||
unleashUrl: string;
|
unleashUrl: string;
|
||||||
serverMetrics: boolean;
|
serverMetrics: boolean;
|
||||||
enableRequestLogger: boolean;
|
enableRequestLogger: boolean;
|
||||||
|
24
src/lib/util/load-index-html.ts
Normal file
24
src/lib/util/load-index-html.ts
Normal file
@ -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<string> {
|
||||||
|
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);
|
||||||
|
}
|
@ -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;
|
let result = input;
|
||||||
result = result.replace(/::baseUriPath::/gi, rewriteValue);
|
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;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,7 @@ process.nextTick(async () => {
|
|||||||
baseUriPath: '',
|
baseUriPath: '',
|
||||||
// keepAliveTimeout: 1,
|
// keepAliveTimeout: 1,
|
||||||
gracefulShutdownEnable: true,
|
gracefulShutdownEnable: true,
|
||||||
|
// cdnPrefix: 'https://cdn.getunleash.io/unleash/v4.4.1',
|
||||||
},
|
},
|
||||||
logLevel: LogLevel.debug,
|
logLevel: LogLevel.debug,
|
||||||
enableOAS: true,
|
enableOAS: true,
|
||||||
|
@ -25,7 +25,3 @@ export function createTestConfig(config?: IUnleashOptions): IUnleashConfig {
|
|||||||
const options = mergeAll<IUnleashOptions>([testConfig, config]);
|
const options = mergeAll<IUnleashOptions>([testConfig, config]);
|
||||||
return createConfig(options);
|
return createConfig(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createTestConfig,
|
|
||||||
};
|
|
||||||
|
@ -18,12 +18,12 @@ export interface IUnleashTest {
|
|||||||
services: IUnleashServices;
|
services: IUnleashServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createApp(
|
async function createApp(
|
||||||
stores,
|
stores,
|
||||||
adminAuthentication = IAuthType.NONE,
|
adminAuthentication = IAuthType.NONE,
|
||||||
preHook?: Function,
|
preHook?: Function,
|
||||||
customOptions?: any,
|
customOptions?: any,
|
||||||
): IUnleashTest {
|
): Promise<IUnleashTest> {
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
authentication: {
|
authentication: {
|
||||||
type: adminAuthentication,
|
type: adminAuthentication,
|
||||||
@ -38,7 +38,7 @@ function createApp(
|
|||||||
const unleashSession = sessionDb(config, undefined);
|
const unleashSession = sessionDb(config, undefined);
|
||||||
const emitter = new EventEmitter();
|
const emitter = new EventEmitter();
|
||||||
emitter.setMaxListeners(0);
|
emitter.setMaxListeners(0);
|
||||||
const app = getApp(config, stores, services, unleashSession);
|
const app = await getApp(config, stores, services, unleashSession);
|
||||||
const request = supertest.agent(app);
|
const request = supertest.agent(app);
|
||||||
|
|
||||||
const destroy = async () => {
|
const destroy = async () => {
|
||||||
|
@ -7246,7 +7246,7 @@ universalify@^0.1.0, universalify@^0.1.2:
|
|||||||
resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz"
|
resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
unleash-frontend@4.4.1:
|
unleash-frontend@v4.4.1:
|
||||||
version "4.4.1"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-4.4.1.tgz#753008e8a1a25b204edf23595261635f030b8590"
|
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-4.4.1.tgz#753008e8a1a25b204edf23595261635f030b8590"
|
||||||
integrity sha512-hyVd56nbWkFdyEeCeHMVZjKlQyWu82QqzGT6IDKzYJruYpIk00/9dm7zycziZIwzu7GXfKkI4J6fnm6Ge7mB5g==
|
integrity sha512-hyVd56nbWkFdyEeCeHMVZjKlQyWu82QqzGT6IDKzYJruYpIk00/9dm7zycziZIwzu7GXfKkI4J6fnm6Ge7mB5g==
|
||||||
|
Loading…
Reference in New Issue
Block a user