1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-31 01:16:01 +02:00

feat: biome lint (#4853)

This commit changes our linter/formatter to biome (https://biomejs.dev/)
Causing our prehook to run almost instantly, and our "yarn lint" task to
run in sub 100ms.

Some trade-offs:
* Biome isn't quite as well established as ESLint
* Are we ready to install a different vscode plugin (the biome plugin)
instead of the prettier plugin


The configuration set for biome also has a set of recommended rules,
this is turned on by default, in order to get to something that was
mergeable I have turned off a couple the rules we seemed to violate the
most, that we also explicitly told eslint to ignore.
This commit is contained in:
Christopher Kolstad 2023-09-29 14:18:21 +02:00 committed by GitHub
parent fbc571dffc
commit 6673d131fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 430 additions and 1593 deletions

View File

@ -1,15 +0,0 @@
node_modules
docker
bundle.js
website/blog
website/build
website/core
website/docs
website/node_modules
website/i18n/*.js
website/pages
website/translated_docs
website
setupJest.js
frontend
dist

View File

@ -1,59 +0,0 @@
{
"env": {
"node": true,
"jest": true
},
"extends": ["airbnb-typescript/base", "plugin:prettier/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2019,
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint", "prettier", "import", "no-only-tests", "regexp"],
"root": true,
"rules": {
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/naming-convention": 0,
"@typescript-eslint/space-before-function-paren": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"class-methods-use-this": [0],
"prettier/prettier": ["error"],
"func-names": "off",
"strict": [0, "global"],
"no-underscore-dangle": "off",
"no-plusplus": "off",
"no-param-reassign": "error",
"no-return-await": "error",
"max-nested-callbacks": "off",
"no-only-tests/no-only-tests": "error",
"new-cap": [
"error",
{
"capIsNewExceptions": ["Router", "Mitm"]
}
],
"regexp/no-super-linear-backtracking": "error",
"regexp/strict": "warn",
"regexp/no-useless-escape": "warn",
"prefer-regex-literals": "warn"
},
"overrides": [
{
// enable the rule specifically for TypeScript files
"files": ["*.ts", "*.tsx"],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": ["error"],
"@typescript-eslint/naming-convention": ["error"],
"@typescript-eslint/space-before-function-paren": ["error"]
}
},
{
"files": ["src/test/e2e/helpers/test-helper.ts"],
"rules": {
"import/no-extraneous-dependencies": "off"
}
}
],
"ignorePatterns": ["**/docs/api/oas/", "examples/**", "scripts/**"]
}

96
biome.json Normal file
View File

@ -0,0 +1,96 @@
{
"$schema": "https://biomejs.dev/schemas/1.2.2/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"performance": {
"noDelete": "off"
},
"correctness": {
"noUnsafeOptionalChaining": "off"
},
"complexity": {
"noBannedTypes": "off",
"noUselessConstructor": "off",
"useOptionalChain": "warn",
"noStaticOnlyClass": "off"
},
"style": {
"noNonNullAssertion": "off",
"noInferrableTypes": "off",
"noUnusedTemplateLiteral": "off",
"useSingleVarDeclarator": "off"
},
"suspicious": {
"noExplicitAny": "off",
"noExtraNonNullAssertion": "off",
"noRedeclare": "off"
}
},
"ignore": [
"node_modules",
"docker",
"bundle.js",
"website/blog",
"website/build",
"website/core",
"website/docs",
"website/i18n/*.js",
"website/pages",
"website/translated_docs",
"website",
"setupJest.js",
"frontend",
"dist",
"src/migrations/*.js",
"src/test/examples/*.json",
"website/**/*.js",
"coverage"
]
},
"organizeImports": {
"enabled": false
},
"formatter": {
"indentStyle": "space",
"ignore": [
"node_modules",
"docker",
"bundle.js",
"website/blog",
"website/build",
"website/core",
"website/docs",
"website/i18n/*.js",
"website/pages",
"website/translated_docs",
"website",
"setupJest.js",
"frontend",
"dist",
"src/migrations/*.js",
"src/migrations/*.json",
"src/test/examples/*.json",
"website/**/*.js",
"coverage",
".eslintrc",
".eslintignore",
"package.json"
],
"indentSize": 4
},
"javascript": {
"formatter": {
"semicolons": "always",
"quoteStyle": "single",
"jsxQuoteStyle": "single",
"indentSize": 4
}
},
"json": {
"formatter": {
"indentSize": 2
}
}
}

View File

@ -44,7 +44,7 @@
"prestart:dev": "yarn run clean", "prestart:dev": "yarn run clean",
"start:dev": "TZ=UTC NODE_ENV=development tsc-watch --strictNullChecks false --onSuccess \"node dist/server-dev.js\"", "start:dev": "TZ=UTC NODE_ENV=development tsc-watch --strictNullChecks false --onSuccess \"node dist/server-dev.js\"",
"db-migrate": "db-migrate --migrations-dir ./src/migrations", "db-migrate": "db-migrate --migrations-dir ./src/migrations",
"lint": "eslint ./src", "lint": "biome check src",
"local:package": "del-cli --force build && mkdir build && cp -r dist docs CHANGELOG.md LICENSE README.md package.json build", "local:package": "del-cli --force build && mkdir build && cp -r dist docs CHANGELOG.md LICENSE README.md package.json build",
"prebuild:watch": "yarn run clean", "prebuild:watch": "yarn run clean",
"build:watch": "tsc -w --strictNullChecks false", "build:watch": "tsc -w --strictNullChecks false",
@ -168,6 +168,7 @@
"@apidevtools/swagger-parser": "10.1.0", "@apidevtools/swagger-parser": "10.1.0",
"@babel/core": "7.22.17", "@babel/core": "7.22.17",
"@swc/core": "1.3.88", "@swc/core": "1.3.88",
"@biomejs/biome": "1.2.2",
"@swc/jest": "0.2.29", "@swc/jest": "0.2.29",
"@types/bcryptjs": "2.4.3", "@types/bcryptjs": "2.4.3",
"@types/cors": "2.8.14", "@types/cors": "2.8.14",
@ -190,20 +191,10 @@
"@types/supertest": "2.0.12", "@types/supertest": "2.0.12",
"@types/type-is": "1.6.4", "@types/type-is": "1.6.4",
"@types/uuid": "9.0.3", "@types/uuid": "9.0.3",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"concurrently": "^8.0.1", "concurrently": "^8.0.1",
"copyfiles": "2.4.1", "copyfiles": "2.4.1",
"coveralls": "3.1.1", "coveralls": "3.1.1",
"del-cli": "5.1.0", "del-cli": "5.1.0",
"eslint": "^8.38.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-prettier": "8.10.0",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-regexp": "^1.14.0",
"faker": "5.5.3", "faker": "5.5.3",
"fast-check": "3.13.0", "fast-check": "3.13.0",
"fetch-mock": "9.11.0", "fetch-mock": "9.11.0",
@ -213,7 +204,6 @@
"lint-staged": "13.2.3", "lint-staged": "13.2.3",
"nock": "13.3.3", "nock": "13.3.3",
"openapi-enforcer": "1.22.3", "openapi-enforcer": "1.22.3",
"prettier": "2.8.1",
"proxyquire": "2.1.3", "proxyquire": "2.1.3",
"source-map-support": "0.5.21", "source-map-support": "0.5.21",
"superagent": "8.1.2", "superagent": "8.1.2",
@ -241,24 +231,10 @@
}, },
"lint-staged": { "lint-staged": {
"*.{js,ts}": [ "*.{js,ts}": [
"eslint --fix" "biome check --apply"
], ],
"*.{json,yaml,md}": [ "*.{json,yaml,md}": [
"prettier --write" "biome format --write"
]
},
"prettier": {
"proseWrap": "never",
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "all",
"overrides": [
{
"files": "*.{json,yaml,yml,md}",
"options": {
"tabWidth": 2
}
}
] ]
} }
} }

View File

@ -9,9 +9,9 @@ function isPrerelease(version) {
return arr && arr.length > 0; return arr && arr.length > 0;
} }
if(isPrerelease(version)){ if (isPrerelease(version)) {
console.log('beta') console.log('beta');
}else if(semver.gt(version, latestUnleashVersion)) { } else if (semver.gt(version, latestUnleashVersion)) {
console.log('latest'); console.log('latest');
} else { } else {
console.log('previous'); console.log('previous');

View File

@ -134,7 +134,7 @@ test('Should call datadog webhook for archived toggle with project info', async
expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
}); });
test(`Should call datadog webhook for toggled environment`, async () => { test('Should call datadog webhook for toggled environment', async () => {
const addon = new DatadogAddon({ const addon = new DatadogAddon({
getLogger: noLogger, getLogger: noLogger,
unleashUrl: 'http://some-url.com', unleashUrl: 'http://some-url.com',
@ -169,7 +169,7 @@ test(`Should call datadog webhook for toggled environment`, async () => {
expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
}); });
test(`Should include customHeaders in headers when calling service`, async () => { test('Should include customHeaders in headers when calling service', async () => {
const addon = new DatadogAddon({ const addon = new DatadogAddon({
getLogger: noLogger, getLogger: noLogger,
unleashUrl: 'http://some-url.com', unleashUrl: 'http://some-url.com',
@ -205,7 +205,7 @@ test(`Should include customHeaders in headers when calling service`, async () =>
expect(fetchRetryCalls[0].options.headers).toMatchSnapshot(); expect(fetchRetryCalls[0].options.headers).toMatchSnapshot();
}); });
test(`Should not include source_type_name when included in the config`, async () => { test('Should not include source_type_name when included in the config', async () => {
const addon = new DatadogAddon({ const addon = new DatadogAddon({
getLogger: noLogger, getLogger: noLogger,
unleashUrl: 'http://some-url.com', unleashUrl: 'http://some-url.com',

View File

@ -72,8 +72,7 @@ export default class DatadogAddon extends Addon {
} }
const { tags: eventTags } = event; const { tags: eventTags } = event;
const tags = const tags = eventTags?.map((tag) => `${tag.type}:${tag.value}`);
eventTags && eventTags.map((tag) => `${tag.type}:${tag.value}`);
const body: DDRequestBody = { const body: DDRequestBody = {
text: text, text: text,
title: 'Unleash notification update', title: 'Unleash notification update',

View File

@ -315,8 +315,7 @@ const testCases: [string, IEvent, string][] = [
].map( ].map(
([operator, display]) => ([operator, display]) =>
<[string, IEvent, string]>[ <[string, IEvent, string]>[
'when default strategy updated with numeric constraint ' + `when default strategy updated with numeric constraint ${operator}`,
operator,
{ {
id: 39, id: 39,
type: FEATURE_STRATEGY_UPDATE, type: FEATURE_STRATEGY_UPDATE,
@ -504,7 +503,7 @@ const testCases: [string, IEvent, string][] = [
]; ];
testCases.forEach(([description, event, expected]) => testCases.forEach(([description, event, expected]) =>
test('Should format specialised text for events ' + description, () => { test(`Should format specialised text for events ${description}`, () => {
const formatter = new FeatureEventFormatterMd('unleashUrl'); const formatter = new FeatureEventFormatterMd('unleashUrl');
const actual = formatter.format(event); const actual = formatter.format(event);
expect(actual).toBe(expected); expect(actual).toBe(expected);

View File

@ -22,10 +22,9 @@ export interface FeatureEventFormatter {
format: (event: IEvent) => string; format: (event: IEvent) => string;
featureLink: (event: IEvent) => string; featureLink: (event: IEvent) => string;
} }
export enum LinkStyle { export enum LinkStyle {
SLACK, SLACK = 0,
MD, MD = 1,
} }
export class FeatureEventFormatterMd implements FeatureEventFormatter { export class FeatureEventFormatterMd implements FeatureEventFormatter {
@ -254,10 +253,11 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter {
SEMVER_LT: 'is a SemVer less than', SEMVER_LT: 'is a SemVer less than',
}; };
const formatConstraint = (constraint: IConstraint) => { const formatConstraint = (constraint: IConstraint) => {
const val = constraint.hasOwnProperty('value') const val = Object.hasOwn(constraint, 'value')
? constraint.value ? constraint.value
: `(${constraint.values.join(',')})`; : `(${constraint.values.join(',')})`;
const operator = constraintOperatorDescriptions.hasOwnProperty( const operator = Object.hasOwn(
constraintOperatorDescriptions,
constraint.operator, constraint.operator,
) )
? constraintOperatorDescriptions[constraint.operator] ? constraintOperatorDescriptions[constraint.operator]

View File

@ -41,7 +41,7 @@ export default async function getApp(
const baseUriPath = config.server.baseUriPath || ''; const baseUriPath = config.server.baseUriPath || '';
const publicFolder = config.publicFolder || findPublicFolder(); const publicFolder = config.publicFolder || findPublicFolder();
let indexHTML = await loadIndexHTML(config, publicFolder); const indexHTML = await loadIndexHTML(config, publicFolder);
app.set('trust proxy', true); app.set('trust proxy', true);
app.disable('x-powered-by'); app.disable('x-powered-by');

View File

@ -439,19 +439,19 @@ test('Environment variables for frontend CORS origins takes priority over option
}); });
test('baseUriPath defaults to the empty string', async () => { test('baseUriPath defaults to the empty string', async () => {
let config = createConfig({}); const config = createConfig({});
expect(config.server.baseUriPath).toBe(''); expect(config.server.baseUriPath).toBe('');
}); });
test('BASE_URI_PATH defined in env is passed through', async () => { test('BASE_URI_PATH defined in env is passed through', async () => {
process.env.BASE_URI_PATH = '/demo'; process.env.BASE_URI_PATH = '/demo';
let config = createConfig({}); const config = createConfig({});
expect(config.server.baseUriPath).toBe('/demo'); expect(config.server.baseUriPath).toBe('/demo');
delete process.env.BASE_URI_PATH; delete process.env.BASE_URI_PATH;
}); });
test('environment variable takes precedence over configured variable', async () => { test('environment variable takes precedence over configured variable', async () => {
process.env.BASE_URI_PATH = '/demo'; process.env.BASE_URI_PATH = '/demo';
let config = createConfig({ const config = createConfig({
server: { server: {
baseUriPath: '/other', baseUriPath: '/other',
}, },
@ -463,7 +463,7 @@ test('environment variable takes precedence over configured variable', async ()
test.each(['demo', '/demo', '/demo/'])( test.each(['demo', '/demo', '/demo/'])(
'Trailing and leading slashes gets normalized for base path %s', 'Trailing and leading slashes gets normalized for base path %s',
async (path) => { async (path) => {
let config = createConfig({ const config = createConfig({
server: { server: {
baseUriPath: path, baseUriPath: path,
}, },
@ -473,7 +473,7 @@ test.each(['demo', '/demo', '/demo/'])(
); );
test('Config with enterpriseVersion set and pro environment should set isEnterprise to false', async () => { test('Config with enterpriseVersion set and pro environment should set isEnterprise to false', async () => {
let config = createConfig({ const config = createConfig({
enterpriseVersion: '5.3.0', enterpriseVersion: '5.3.0',
ui: { environment: 'pro' }, ui: { environment: 'pro' },
}); });
@ -481,7 +481,7 @@ test('Config with enterpriseVersion set and pro environment should set isEnterpr
}); });
test('Config with enterpriseVersion set and not pro environment should set isEnterprise to true', async () => { test('Config with enterpriseVersion set and not pro environment should set isEnterprise to true', async () => {
let config = createConfig({ const config = createConfig({
enterpriseVersion: '5.3.0', enterpriseVersion: '5.3.0',
ui: { environment: 'Enterprise' }, ui: { environment: 'Enterprise' },
}); });

View File

@ -78,7 +78,7 @@ const defaultClientCachingOptions: IClientCachingOption = {
function loadClientCachingOptions( function loadClientCachingOptions(
options: IUnleashOptions, options: IUnleashOptions,
): IClientCachingOption { ): IClientCachingOption {
let envs: Partial<IClientCachingOption> = {}; const envs: Partial<IClientCachingOption> = {};
if (process.env.CLIENT_FEATURE_CACHING_MAXAGE) { if (process.env.CLIENT_FEATURE_CACHING_MAXAGE) {
envs.maxAge = parseEnvVarNumber( envs.maxAge = parseEnvVarNumber(
process.env.CLIENT_FEATURE_CACHING_MAXAGE, process.env.CLIENT_FEATURE_CACHING_MAXAGE,

View File

@ -848,8 +848,7 @@ export class AccessStore implements IAccessStore {
} }
async getUserAccessOverview(): Promise<IUserAccessOverview[]> { async getUserAccessOverview(): Promise<IUserAccessOverview[]> {
const result = await this.db const result = await this.db.raw(`SELECT u.id, u.created_at, u.name, u.email, u.seen_at, up.p_array as projects, gr.p_array as groups, gp.p_array as group_projects, r.name as root_role
.raw(`SELECT u.id, u.created_at, u.name, u.email, u.seen_at, up.p_array as projects, gr.p_array as groups, gp.p_array as group_projects, r.name as root_role
FROM users u, LATERAL ( FROM users u, LATERAL (
SELECT ARRAY ( SELECT ARRAY (
SELECT ru.project SELECT ru.project

View File

@ -104,9 +104,7 @@ const remapUsageRow = (input) => {
}; };
}; };
export default class ClientApplicationsStore export default class ClientApplicationsStore implements IClientApplicationsStore {
implements IClientApplicationsStore
{
private db: Db; private db: Db;
private logger: Logger; private logger: Logger;

View File

@ -158,7 +158,7 @@ export class ClientMetricsStoreV2 implements IClientMetricsStoreV2 {
// this function will collapse metrics before sending it to the database. // this function will collapse metrics before sending it to the database.
async batchInsertMetrics(metrics: IClientMetricsEnv[]): Promise<void> { async batchInsertMetrics(metrics: IClientMetricsEnv[]): Promise<void> {
if (!metrics || metrics.length == 0) { if (!metrics || metrics.length === 0) {
return; return;
} }
const rows = collapseHourlyMetrics(metrics).map(toRow); const rows = collapseHourlyMetrics(metrics).map(toRow);

View File

@ -47,7 +47,7 @@ test('Find unannounced events returns all events', async () => {
await db.rawDatabase('events').insert(allEvents).returning(['id']); await db.rawDatabase('events').insert(allEvents).returning(['id']);
const store = new EventStore(db.rawDatabase, getLogger); const store = new EventStore(db.rawDatabase, getLogger);
let events = await store.setUnannouncedToAnnounced(); const events = await store.setUnannouncedToAnnounced();
expect(events).toBeTruthy(); expect(events).toBeTruthy();
expect(events.length).toBe(505); expect(events.length).toBe(505);
await db.destroy(); await db.destroy();

View File

@ -107,7 +107,7 @@ class EventStore implements IEventStore {
} }
async count(): Promise<number> { async count(): Promise<number> {
let count = await this.db(TABLE) const count = await this.db(TABLE)
.count<Record<string, number>>() .count<Record<string, number>>()
.first(); .first();
if (!count) { if (!count) {
@ -131,7 +131,7 @@ class EventStore implements IEventStore {
if (eventSearch.feature) { if (eventSearch.feature) {
query = query.andWhere({ feature_name: eventSearch.feature }); query = query.andWhere({ feature_name: eventSearch.feature });
} }
let count = await query.count().first(); const count = await query.count().first();
if (!count) { if (!count) {
return 0; return 0;
} }

View File

@ -389,7 +389,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
environments: string[], environments: string[],
variants: IVariant[], variants: IVariant[],
): Promise<void> { ): Promise<void> {
let v = variants || []; const v = variants || [];
v.sort((a, b) => a.name.localeCompare(b.name)); v.sort((a, b) => a.name.localeCompare(b.name));
const variantsString = JSON.stringify(v); const variantsString = JSON.stringify(v);
const records = environments.map((env) => ({ const records = environments.map((env) => ({
@ -407,7 +407,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
async addFeatureEnvironment( async addFeatureEnvironment(
featureEnvironment: IFeatureEnvironment, featureEnvironment: IFeatureEnvironment,
): Promise<void> { ): Promise<void> {
let v = featureEnvironment.variants || []; const v = featureEnvironment.variants || [];
v.sort((a, b) => a.name.localeCompare(b.name)); v.sort((a, b) => a.name.localeCompare(b.name));
await this.db(T.featureEnvs) await this.db(T.featureEnvs)
.insert({ .insert({
@ -424,11 +424,11 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
sourceEnvironment: string, sourceEnvironment: string,
destinationEnvironment: string, destinationEnvironment: string,
): Promise<void> { ): Promise<void> {
let sourceFeatureStrategies = await this.db('feature_strategies').where( const sourceFeatureStrategies = await this.db(
{ 'feature_strategies',
environment: sourceEnvironment, ).where({
}, environment: sourceEnvironment,
); });
const clonedStrategyRows = sourceFeatureStrategies.map( const clonedStrategyRows = sourceFeatureStrategies.map(
(featureStrategy) => { (featureStrategy) => {

View File

@ -493,10 +493,10 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
.whereIn(['tag_type', 'tag_value'], tag); .whereIn(['tag_type', 'tag_value'], tag);
query = query.whereIn('features.name', tagQuery); query = query.whereIn('features.name', tagQuery);
} }
if (namePrefix && namePrefix.trim()) { if (namePrefix?.trim()) {
let namePrefixQuery = namePrefix; let namePrefixQuery = namePrefix;
if (!namePrefix.endsWith('%')) { if (!namePrefix.endsWith('%')) {
namePrefixQuery = namePrefixQuery + '%'; namePrefixQuery = `${namePrefixQuery}%`;
} }
query = query.whereILike('features.name', namePrefixQuery); query = query.whereILike('features.name', namePrefixQuery);
} }

View File

@ -184,7 +184,7 @@ export default class FeatureToggleClientStore
stopTimer(); stopTimer();
const featureToggles = rows.reduce((acc, r) => { const featureToggles = rows.reduce((acc, r) => {
let feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? { const feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
strategies: [], strategies: [],
}; };
if (this.isUnseenStrategyRow(feature, r) && !r.strategy_disabled) { if (this.isUnseenStrategyRow(feature, r) && !r.strategy_disabled) {

View File

@ -122,7 +122,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
dateAccessor: string; dateAccessor: string;
}): Promise<number> { }): Promise<number> {
const { project, archived, dateAccessor } = queryModifiers; const { project, archived, dateAccessor } = queryModifiers;
let query = this.db const query = this.db
.count() .count()
.from(TABLE) .from(TABLE)
.where({ project }) .where({ project })

View File

@ -361,7 +361,7 @@ class ProjectStore implements IProjectStore {
async getProjectLinksForEnvironments( async getProjectLinksForEnvironments(
environments: string[], environments: string[],
): Promise<IEnvironmentProjectLink[]> { ): Promise<IEnvironmentProjectLink[]> {
let rows = await this.db('project_environments') const rows = await this.db('project_environments')
.select(['project_id', 'environment_name']) .select(['project_id', 'environment_name'])
.whereIn('environment_name', environments); .whereIn('environment_name', environments);
return rows.map(this.mapLinkRow); return rows.map(this.mapLinkRow);

View File

@ -17,11 +17,11 @@ class BadDataError extends UnleashError {
message: string, message: string,
errors?: [ValidationErrorDescription, ...ValidationErrorDescription[]], errors?: [ValidationErrorDescription, ...ValidationErrorDescription[]],
) { ) {
const topLevelMessage = const topLevelMessage = `Request validation failed: your request body or params contain invalid data${
'Request validation failed: your request body or params contain invalid data' + errors
(errors
? '. Refer to the `details` list for more information.' ? '. Refer to the `details` list for more information.'
: `: ${message}`); : `: ${message}`
}`;
super(topLevelMessage); super(topLevelMessage);
this.details = errors ?? [{ message: message, description: message }]; this.details = errors ?? [{ message: message, description: message }];

View File

@ -23,8 +23,6 @@ const getStatusCode = (errorName: string): number => {
return 400; return 400;
case 'InvalidTokenError': case 'InvalidTokenError':
return 401; return 401;
case 'NoAccessError':
return 403;
case 'UsedTokenError': case 'UsedTokenError':
return 403; return 403;
case 'InvalidOperationError': case 'InvalidOperationError':

View File

@ -44,10 +44,9 @@ export class ImportTogglesStore implements IImportTogglesStore {
environment: string, environment: string,
): Promise<boolean> { ): Promise<boolean> {
if (featureNames.length === 0) return true; if (featureNames.length === 0) return true;
const joinedFeatureNames = featureNames.map(() => '?').join(',');
const result = await this.db.raw( const result = await this.db.raw(
'SELECT EXISTS (SELECT 1 FROM feature_strategies WHERE environment = ? and feature_name in (' + `SELECT EXISTS (SELECT 1 FROM feature_strategies WHERE environment = ? and feature_name in (${joinedFeatureNames})) AS present`,
featureNames.map(() => '?').join(',') +
')) AS present',
[environment, ...featureNames], [environment, ...featureNames],
); );
const { present } = result.rows[0]; const { present } = result.rows[0];

View File

@ -30,7 +30,7 @@ export function resolveContextValue(
if (context[field]) { if (context[field]) {
return context[field] as string; return context[field] as string;
} }
if (context.properties && context.properties[field]) { if (context.properties?.[field]) {
return context.properties[field] as string; return context.properties[field] as string;
} }
return undefined; return undefined;

View File

@ -35,14 +35,12 @@ export const mapFeaturesForClient = (
type: variant.payload.type as PayloadType, type: variant.payload.type as PayloadType,
}, },
})), })),
constraints: constraints: strategy.constraints?.map((constraint) => ({
strategy.constraints && inverted: false,
strategy.constraints.map((constraint) => ({ values: [],
inverted: false, ...constraint,
values: [], operator: constraint.operator as unknown as Operator,
...constraint, })),
operator: constraint.operator as unknown as Operator,
})),
})), })),
})); }));

View File

@ -1,5 +1,4 @@
import { IPrivateProjectChecker } from './privateProjectCheckerType'; import { IPrivateProjectChecker } from './privateProjectCheckerType';
import { Promise } from 'ts-toolbelt/out/Any/Promise';
import { ProjectAccess } from './privateProjectStore'; import { ProjectAccess } from './privateProjectStore';
export class FakePrivateProjectChecker implements IPrivateProjectChecker { export class FakePrivateProjectChecker implements IPrivateProjectChecker {

View File

@ -42,7 +42,7 @@ class PrivateProjectStore implements IPrivateProjectStore {
.count('*') .count('*')
.first(); .first();
if (!isViewer || isViewer.count == 0) { if (!isViewer || isViewer.count === 0) {
return ALL_PROJECT_ACCESS; return ALL_PROJECT_ACCESS;
} }

View File

@ -351,7 +351,7 @@ export default class MetricsMonitor {
} }
configureDbMetrics(db: Knex, eventBus: EventEmitter): void { configureDbMetrics(db: Knex, eventBus: EventEmitter): void {
if (db && db.client) { if (db?.client) {
const dbPoolMin = new client.Gauge({ const dbPoolMin = new client.Gauge({
name: 'db_pool_min', name: 'db_pool_min',
help: 'Minimum DB pool size', help: 'Minimum DB pool size',

View File

@ -13,7 +13,7 @@ const authorizationMiddleware = (
logger.debug('Enabling Authorization middleware'); logger.debug('Enabling Authorization middleware');
return async (req: IAuthRequest, res: Response, next: NextFunction) => { return async (req: IAuthRequest, res: Response, next: NextFunction) => {
if (req.session && req.session.user) { if (req.session?.user) {
req.user = req.session.user; req.user = req.session.user;
return next(); return next();
} }

View File

@ -7,7 +7,7 @@ import { ApiTokenType } from '../types/models/api-token';
function demoAuthentication( function demoAuthentication(
app: Application, app: Application,
basePath: string = '', // eslint-disable-line basePath: string, // eslint-disable-line
{ userService }: Pick<IUnleashServices, 'userService'>, { userService }: Pick<IUnleashServices, 'userService'>,
{ authentication }: Pick<IUnleashConfig, 'authentication'>, { authentication }: Pick<IUnleashConfig, 'authentication'>,
): void { ): void {
@ -30,7 +30,7 @@ function demoAuthentication(
app.use(`${basePath}/api/admin/`, (req, res, next) => { app.use(`${basePath}/api/admin/`, (req, res, next) => {
// @ts-expect-error // @ts-expect-error
if (req.session.user && req.session.user.email) { if (req.session.user?.email) {
// @ts-expect-error // @ts-expect-error
req.user = req.session.user; req.user = req.session.user;
} }

View File

@ -2,8 +2,8 @@ import { Application } from 'express';
import NoAuthUser from '../types/no-auth-user'; import NoAuthUser from '../types/no-auth-user';
// eslint-disable-next-line // eslint-disable-next-line
function noneAuthentication(basePath = '', app: Application): void { function noneAuthentication(basePath: string, app: Application): void {
app.use(`${basePath}/api/admin/`, (req, res, next) => { app.use(`${basePath || ''}/api/admin/`, (req, res, next) => {
// @ts-expect-error // @ts-expect-error
if (!req.user) { if (!req.user) {
// @ts-expect-error // @ts-expect-error

View File

@ -111,7 +111,7 @@ test('should call next if accountService throws exception', async () => {
}); });
test('Should not log at error level if user not found', async () => { test('Should not log at error level if user not found', async () => {
let fakeLogger = { const fakeLogger = {
debug: () => {}, debug: () => {},
info: () => {}, info: () => {},
warn: jest.fn(), warn: jest.fn(),
@ -131,7 +131,7 @@ test('Should not log at error level if user not found', async () => {
throw new NotFoundError('Could not find pat'); throw new NotFoundError('Could not find pat');
}), }),
}; };
let mw = patMiddleware(conf, { accountService }); const mw = patMiddleware(conf, { accountService });
const cb = jest.fn(); const cb = jest.fn();
const req = { const req = {

View File

@ -62,7 +62,7 @@ const rbacMiddleware = (
let projectId = let projectId =
findParam('projectId', req) || findParam('project', req); findParam('projectId', req) || findParam('project', req);
let environment = const environment =
findParam('environment', req) || findParam('environment', req) ||
findParam('environmentId', req); findParam('environmentId', req);
@ -80,7 +80,7 @@ const rbacMiddleware = (
projectId === undefined && projectId === undefined &&
permissionsArray.some( permissionsArray.some(
(permission) => (permission) =>
permission == CREATE_FEATURE || permission === CREATE_FEATURE ||
permission.endsWith('FEATURE_STRATEGY'), permission.endsWith('FEATURE_STRATEGY'),
) )
) { ) {

View File

@ -18,8 +18,9 @@ test('all schema files should be added to the schemas object', () => {
}); });
test('removeJsonSchemaProps', () => { test('removeJsonSchemaProps', () => {
expect(removeJsonSchemaProps({ a: 'b', $id: 'c', components: {} })) expect(
.toMatchInlineSnapshot(` removeJsonSchemaProps({ a: 'b', $id: 'c', components: {} }),
).toMatchInlineSnapshot(`
{ {
"a": "b", "a": "b",
} }
@ -41,7 +42,7 @@ describe('createOpenApiSchema', () => {
createOpenApiSchema({ createOpenApiSchema({
unleashUrl: 'https://example.com/demo2', unleashUrl: 'https://example.com/demo2',
baseUriPath: '/demo2', baseUriPath: '/demo2',
}).servers![0].url, }).servers?.[0].url,
).toEqual('https://example.com/demo2'); ).toEqual('https://example.com/demo2');
}); });
@ -50,7 +51,7 @@ describe('createOpenApiSchema', () => {
createOpenApiSchema({ createOpenApiSchema({
unleashUrl: 'https://example.com/demo2', unleashUrl: 'https://example.com/demo2',
baseUriPath: 'example', baseUriPath: 'example',
}).servers![0].url, }).servers?.[0].url,
).toEqual('https://example.com/demo2'); ).toEqual('https://example.com/demo2');
}); });
@ -59,13 +60,13 @@ describe('createOpenApiSchema', () => {
createOpenApiSchema({ createOpenApiSchema({
unleashUrl: 'https://example.com/example/', unleashUrl: 'https://example.com/example/',
baseUriPath: 'example', baseUriPath: 'example',
}).servers![0].url, }).servers?.[0].url,
).toEqual('https://example.com'); ).toEqual('https://example.com');
expect( expect(
createOpenApiSchema({ createOpenApiSchema({
unleashUrl: 'https://example.com/example/', unleashUrl: 'https://example.com/example/',
baseUriPath: '/example', baseUriPath: '/example',
}).servers![0].url, }).servers?.[0].url,
).toEqual('https://example.com/example'); ).toEqual('https://example.com/example');
}); });
}); });

View File

@ -58,7 +58,7 @@ export const resourceCreatedResponseSchema = (
}, },
}, },
}, },
description: `The resource was successfully created.`, description: 'The resource was successfully created.',
content: { content: {
'application/json': { 'application/json': {
schema: { schema: {

View File

@ -24,10 +24,7 @@ type Services = Pick<
'featureToggleServiceV2' | 'segmentService' | 'configurationRevisionService' 'featureToggleServiceV2' | 'segmentService' | 'configurationRevisionService'
>; >;
export class ProxyRepository export class ProxyRepository extends EventEmitter implements RepositoryInterface {
extends EventEmitter
implements RepositoryInterface
{
private readonly config: Config; private readonly config: Config;
private readonly logger: Logger; private readonly logger: Logger;

View File

@ -51,46 +51,44 @@ interface TokenParam {
interface TokenNameParam { interface TokenNameParam {
name: string; name: string;
} }
export const tokenTypeToCreatePermission: ( export const tokenTypeToCreatePermission: (tokenType: ApiTokenType) => string =
tokenType: ApiTokenType, (tokenType) => {
) => string = (tokenType) => { switch (tokenType) {
switch (tokenType) { case ApiTokenType.ADMIN:
case ApiTokenType.ADMIN: return ADMIN;
return ADMIN; case ApiTokenType.CLIENT:
case ApiTokenType.CLIENT: return CREATE_CLIENT_API_TOKEN;
return CREATE_CLIENT_API_TOKEN; case ApiTokenType.FRONTEND:
case ApiTokenType.FRONTEND: return CREATE_FRONTEND_API_TOKEN;
return CREATE_FRONTEND_API_TOKEN; }
} };
};
const permissionToTokenType: ( const permissionToTokenType: (permission: string) => ApiTokenType | undefined =
permission: string, (permission) => {
) => ApiTokenType | undefined = (permission) => { if (
if ( [
[ CREATE_FRONTEND_API_TOKEN,
CREATE_FRONTEND_API_TOKEN, READ_FRONTEND_API_TOKEN,
READ_FRONTEND_API_TOKEN, DELETE_FRONTEND_API_TOKEN,
DELETE_FRONTEND_API_TOKEN, UPDATE_FRONTEND_API_TOKEN,
UPDATE_FRONTEND_API_TOKEN, ].includes(permission)
].includes(permission) ) {
) { return ApiTokenType.FRONTEND;
return ApiTokenType.FRONTEND; } else if (
} else if ( [
[ CREATE_CLIENT_API_TOKEN,
CREATE_CLIENT_API_TOKEN, READ_CLIENT_API_TOKEN,
READ_CLIENT_API_TOKEN, DELETE_CLIENT_API_TOKEN,
DELETE_CLIENT_API_TOKEN, UPDATE_CLIENT_API_TOKEN,
UPDATE_CLIENT_API_TOKEN, ].includes(permission)
].includes(permission) ) {
) { return ApiTokenType.CLIENT;
return ApiTokenType.CLIENT; } else if (ADMIN === permission) {
} else if (ADMIN === permission) { return ApiTokenType.ADMIN;
return ApiTokenType.ADMIN; } else {
} else { return undefined;
return undefined; }
} };
};
const tokenTypeToUpdatePermission: (tokenType: ApiTokenType) => string = ( const tokenTypeToUpdatePermission: (tokenType: ApiTokenType) => string = (
tokenType, tokenType,
@ -386,7 +384,7 @@ export class ApiTokenController extends Controller {
const permissionRequired = tokenTypeToDeletePermission( const permissionRequired = tokenTypeToDeletePermission(
tokenToUpdate.type, tokenToUpdate.type,
); );
let hasPermission = await this.accessService.hasPermission( const hasPermission = await this.accessService.hasPermission(
req.user, req.user,
permissionRequired, permissionRequired,
); );

View File

@ -120,7 +120,7 @@ class ConfigController extends Controller {
const disablePasswordAuth = const disablePasswordAuth =
simpleAuthSettings?.disabled || simpleAuthSettings?.disabled ||
this.config.authentication.type == IAuthType.NONE; this.config.authentication.type === IAuthType.NONE;
const expFlags = this.config.flagResolver.getAll({ const expFlags = this.config.flagResolver.getAll({
email: req.user.email, email: req.user.email,

View File

@ -661,8 +661,8 @@ export default class ProjectFeaturesController extends Controller {
featureName, featureName,
projectId, projectId,
name, name,
replaceGroupId,
userName, userName,
replaceGroupId,
); );
this.openApiService.respondWithValidation( this.openApiService.respondWithValidation(

View File

@ -63,7 +63,7 @@ describe('Public Signup API', () => {
destroy(); destroy();
}); });
const expireAt = (addDays: number = 7): Date => { const expireAt = (addDays: number = 7): Date => {
let now = new Date(); const now = new Date();
now.setDate(now.getDate() + addDays); now.setDate(now.getDate() + addDays);
return now; return now;
}; };

View File

@ -425,7 +425,7 @@ export default class UserAdminController extends Controller {
req: Request, req: Request,
res: Response<UsersGroupsBaseSchema>, res: Response<UsersGroupsBaseSchema>,
): Promise<void> { ): Promise<void> {
let allUsers = await this.accountService.getAll(); const allUsers = await this.accountService.getAll();
let users = allUsers.map((u) => { let users = allUsers.map((u) => {
return { return {
id: u.id, id: u.id,
@ -439,8 +439,8 @@ export default class UserAdminController extends Controller {
users = this.anonymiseUsers(users); users = this.anonymiseUsers(users);
} }
let allGroups = await this.groupService.getAll(); const allGroups = await this.groupService.getAll();
let groups = allGroups.map((g) => { const groups = allGroups.map((g) => {
return { return {
id: g.id, id: g.id,
name: g.name, name: g.name,

View File

@ -39,8 +39,7 @@ interface IRouteOptionsNonGet extends IRouteOptionsBase {
type IRouteOptions = IRouteOptionsNonGet | IRouteOptionsGet; type IRouteOptions = IRouteOptionsNonGet | IRouteOptionsGet;
const checkPermission = const checkPermission =
(permission: Permission = []) => (permission: Permission = []) => async (req, res, next) => {
async (req, res, next) => {
const permissions = ( const permissions = (
Array.isArray(permission) ? permission : [permission] Array.isArray(permission) ? permission : [permission]
).filter((p) => p !== NONE); ).filter((p) => p !== NONE);

View File

@ -118,7 +118,7 @@ export default class EdgeController extends Controller {
const { metrics, applications } = body; const { metrics, applications } = body;
try { try {
let promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
for (const app of applications) { for (const app of applications) {
promises.push( promises.push(
this.clientInstanceService.registerClient(app, clientIp), this.clientInstanceService.registerClient(app, clientIp),

View File

@ -279,10 +279,10 @@ test('Should destroy sessions for user', async () => {
}, },
expired: addDays(new Date(), 2), expired: addDays(new Date(), 2),
}); });
let activeSessionsBeforeLogout = await sessionStore.getSessionsForUser(1); const activeSessionsBeforeLogout = await sessionStore.getSessionsForUser(1);
expect(activeSessionsBeforeLogout).toHaveLength(2); expect(activeSessionsBeforeLogout).toHaveLength(2);
app.use('/logout', new LogoutController(config, { sessionService }).router); app.use('/logout', new LogoutController(config, { sessionService }).router);
await supertest(app).post('/logout').expect(302); await supertest(app).post('/logout').expect(302);
let activeSessions = await sessionStore.getSessionsForUser(1); const activeSessions = await sessionStore.getSessionsForUser(1);
expect(activeSessions).toHaveLength(0); expect(activeSessions).toHaveLength(0);
}); });

View File

@ -69,7 +69,7 @@ describe('Public Signup API', () => {
destroy(); destroy();
}); });
const expireAt = (addDays: number = 7): Date => { const expireAt = (addDays: number = 7): Date => {
let now = new Date(); const now = new Date();
now.setDate(now.getDate() + addDays); now.setDate(now.getDate() + addDays);
return now; return now;
}; };

View File

@ -18,7 +18,7 @@ test('should reject token with projects and project', async () => {
}); });
test('should not have default project set if projects is present', async () => { test('should not have default project set if projects is present', async () => {
let token = await createApiToken.validateAsync({ const token = await createApiToken.validateAsync({
username: 'test', username: 'test',
type: 'admin', type: 'admin',
projects: ['default'], projects: ['default'],
@ -27,7 +27,7 @@ test('should not have default project set if projects is present', async () => {
}); });
test('should have project set to default if projects is missing', async () => { test('should have project set to default if projects is missing', async () => {
let token = await createApiToken.validateAsync({ const token = await createApiToken.validateAsync({
username: 'test', username: 'test',
type: 'admin', type: 'admin',
}); });
@ -35,7 +35,7 @@ test('should have project set to default if projects is missing', async () => {
}); });
test('should not have projects set if project is present', async () => { test('should not have projects set if project is present', async () => {
let token = await createApiToken.validateAsync({ const token = await createApiToken.validateAsync({
username: 'test', username: 'test',
type: 'admin', type: 'admin',
project: 'default', project: 'default',
@ -44,7 +44,7 @@ test('should not have projects set if project is present', async () => {
}); });
test('should allow for embedded proxy (frontend) key', async () => { test('should allow for embedded proxy (frontend) key', async () => {
let token = await createApiToken.validateAsync({ const token = await createApiToken.validateAsync({
username: 'test', username: 'test',
type: 'frontend', type: 'frontend',
project: 'default', project: 'default',
@ -53,7 +53,7 @@ test('should allow for embedded proxy (frontend) key', async () => {
}); });
test('should set environment to default for frontend key', async () => { test('should set environment to default for frontend key', async () => {
let token = await createApiToken.validateAsync({ const token = await createApiToken.validateAsync({
username: 'test', username: 'test',
type: 'frontend', type: 'frontend',
project: 'default', project: 'default',

View File

@ -113,5 +113,5 @@ test('should shutdown the server when calling stop()', async () => {
createTestConfig({ server: { port: 0 } }), createTestConfig({ server: { port: 0 } }),
); );
await stop(); await stop();
expect(server!.address()).toBe(null); expect(server?.address()).toBe(null);
}); });

View File

@ -500,7 +500,7 @@ export class AccessService {
const userIdList = userRoleList.map((u) => u.userId); const userIdList = userRoleList.map((u) => u.userId);
const users = await this.accountStore.getAllWithId(userIdList); const users = await this.accountStore.getAllWithId(userIdList);
return users.map((user) => { return users.map((user) => {
const role = userRoleList.find((r) => r.userId == user.id)!; const role = userRoleList.find((r) => r.userId === user.id)!;
return { return {
...user, ...user,
addedAt: role.addedAt!, addedAt: role.addedAt!,

View File

@ -113,7 +113,7 @@ export default class AddonService {
(addon) => (addon) =>
!event.project || !event.project ||
!addon.projects || !addon.projects ||
addon.projects.length == 0 || addon.projects.length === 0 ||
addon.projects[0] === WILDCARD_OPTION || addon.projects[0] === WILDCARD_OPTION ||
addon.projects.includes(event.project), addon.projects.includes(event.project),
) )
@ -121,7 +121,7 @@ export default class AddonService {
(addon) => (addon) =>
!event.environment || !event.environment ||
!addon.environments || !addon.environments ||
addon.environments.length == 0 || addon.environments.length === 0 ||
addon.environments[0] === WILDCARD_OPTION || addon.environments[0] === WILDCARD_OPTION ||
addon.environments.includes(event.environment), addon.environments.includes(event.environment),
) )

View File

@ -133,8 +133,8 @@ test('Api token operations should all have events attached', async () => {
config, config,
eventService, eventService,
); );
let saved = await apiTokenService.createApiTokenWithProjects(token); const saved = await apiTokenService.createApiTokenWithProjects(token);
let newExpiry = addDays(new Date(), 30); const newExpiry = addDays(new Date(), 30);
await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test'); await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test');
await apiTokenService.delete(saved.secret, 'test'); await apiTokenService.delete(saved.secret, 'test');
const { events } = await eventService.getEvents(); const { events } = await eventService.getEvents();

View File

@ -81,7 +81,7 @@ export class ApiTokenService {
try { try {
this.activeTokens = await this.getAllActiveTokens(); this.activeTokens = await this.getAllActiveTokens();
} finally { } finally {
// eslint-disable-next-line no-unsafe-finally // biome-ignore lint/correctness/noUnsafeFinally: We ignored this for eslint. Leaving this here for now, server-impl test fails without it
return; return;
} }
} }
@ -259,7 +259,7 @@ export class ApiTokenService {
if (!errorDetails) { if (!errorDetails) {
return 'invalid'; return 'invalid';
} }
let invalidProject = projects.find((project) => { const invalidProject = projects.find((project) => {
return errorDetails.includes(`=(${project})`); return errorDetails.includes(`=(${project})`);
}); });
return invalidProject || 'invalid'; return invalidProject || 'invalid';

View File

@ -43,7 +43,7 @@ export class EmailService {
constructor(email: IEmailOption, getLogger: LogProvider) { constructor(email: IEmailOption, getLogger: LogProvider) {
this.logger = getLogger('services/email-service.ts'); this.logger = getLogger('services/email-service.ts');
if (email && email.host) { if (email?.host) {
this.sender = email.sender; this.sender = email.sender;
if (email.host === 'test') { if (email.host === 'test') {
this.mailer = createTransport({ jsonTransport: true }); this.mailer = createTransport({ jsonTransport: true });

View File

@ -152,12 +152,9 @@ export default class EnvironmentService {
} }
const environmentsNotAlreadyEnabled = const environmentsNotAlreadyEnabled =
existingEnvironmentsToEnable.filter((env) => env.enabled == false); existingEnvironmentsToEnable.filter((env) => !env.enabled);
const environmentsToDisable = allEnvironments.filter((env) => { const environmentsToDisable = allEnvironments.filter((env) => {
return ( return !environmentNamesToEnable.includes(env.name) && env.enabled;
!environmentNamesToEnable.includes(env.name) &&
env.enabled == true
);
}); });
await this.environmentStore.disable(environmentsToDisable); await this.environmentStore.disable(environmentsToDisable);
@ -190,13 +187,13 @@ export default class EnvironmentService {
...new Set(projectLinks.map((link) => link.projectId)), ...new Set(projectLinks.map((link) => link.projectId)),
]; ];
let linkTasks = uniqueProjects.map((project) => { const linkTasks = uniqueProjects.flatMap((project) => {
return toEnable.map((enabledEnv) => { return toEnable.map((enabledEnv) => {
return this.addEnvironmentToProject(enabledEnv.name, project); return this.addEnvironmentToProject(enabledEnv.name, project);
}); });
}); });
await Promise.all(linkTasks.flat()); await Promise.all(linkTasks);
} }
async forceRemoveEnvironmentFromProject( async forceRemoveEnvironmentFromProject(

View File

@ -27,8 +27,8 @@ export default class EventService {
} }
async getEvents(): Promise<IEventList> { async getEvents(): Promise<IEventList> {
let totalEvents = await this.eventStore.count(); const totalEvents = await this.eventStore.count();
let events = await this.eventStore.getEvents(); const events = await this.eventStore.getEvents();
return { return {
events, events,
totalEvents, totalEvents,
@ -36,8 +36,8 @@ export default class EventService {
} }
async searchEvents(search: SearchEventsSchema): Promise<IEventList> { async searchEvents(search: SearchEventsSchema): Promise<IEventList> {
let totalEvents = await this.eventStore.filteredCount(search); const totalEvents = await this.eventStore.filteredCount(search);
let events = await this.eventStore.searchEvents(search); const events = await this.eventStore.searchEvents(search);
return { return {
events, events,
totalEvents, totalEvents,

View File

@ -348,8 +348,7 @@ class FeatureToggleService {
} }
if ( if (
contextDefinition && contextDefinition?.legalValues &&
contextDefinition.legalValues &&
contextDefinition.legalValues.length > 0 contextDefinition.legalValues.length > 0
) { ) {
const valuesToValidate = oneOf( const valuesToValidate = oneOf(
@ -1181,8 +1180,8 @@ class FeatureToggleService {
featureName: string, featureName: string,
projectId: string, projectId: string,
newFeatureName: string, newFeatureName: string,
replaceGroupId: boolean = true, // eslint-disable-line
userName: string, userName: string,
replaceGroupId: boolean = true,
): Promise<FeatureToggle> { ): Promise<FeatureToggle> {
const changeRequestEnabled = const changeRequestEnabled =
await this.changeRequestAccessReadModel.isChangeRequestsEnabledForProject( await this.changeRequestAccessReadModel.isChangeRequestsEnabledForProject(
@ -1227,7 +1226,7 @@ class FeatureToggleService {
if ( if (
replaceGroupId && replaceGroupId &&
s.parameters && s.parameters &&
s.parameters.hasOwnProperty('groupId') Object.hasOwn(s.parameters, 'groupId')
) { ) {
s.parameters.groupId = newFeatureName; s.parameters.groupId = newFeatureName;
} }
@ -1890,7 +1889,9 @@ class FeatureToggleService {
env.name, env.name,
newVariants, newVariants,
user, user,
).then((resultingVariants) => (env.variants = resultingVariants)), ).then((resultingVariants) => {
env.variants = resultingVariants;
}),
); );
await Promise.all(promises); await Promise.all(promises);
ft.variants = ft.environments[0].variants; ft.variants = ft.environments[0].variants;
@ -2077,11 +2078,11 @@ class FeatureToggleService {
); );
} }
let fixedVariants = variants.filter((x) => { const fixedVariants = variants.filter((x) => {
return x.weightType === WeightType.FIX; return x.weightType === WeightType.FIX;
}); });
let fixedWeights = fixedVariants.reduce((a, v) => a + v.weight, 0); const fixedWeights = fixedVariants.reduce((a, v) => a + v.weight, 0);
if (fixedWeights > 1000) { if (fixedWeights > 1000) {
throw new BadDataError( throw new BadDataError(
@ -2089,7 +2090,7 @@ class FeatureToggleService {
); );
} }
let averageWeight = Math.floor( const averageWeight = Math.floor(
(1000 - fixedWeights) / variableVariants.length, (1000 - fixedWeights) / variableVariants.length,
); );
let remainder = (1000 - fixedWeights) % variableVariants.length; let remainder = (1000 - fixedWeights) % variableVariants.length;

View File

@ -118,7 +118,7 @@ export class GroupService {
const deletableUsers = existingUsers.filter( const deletableUsers = existingUsers.filter(
(existingUser) => (existingUser) =>
!group.users.some( !group.users.some(
(groupUser) => groupUser.user.id == existingUser.userId, (groupUser) => groupUser.user.id === existingUser.userId,
), ),
); );
@ -188,7 +188,7 @@ export class GroupService {
throw new BadDataError('Group name cannot be empty'); throw new BadDataError('Group name cannot be empty');
} }
if (!existingGroup || existingGroup.name != group.name) { if (!existingGroup || existingGroup.name !== group.name) {
if (await this.groupStore.existsWithName(group.name)) { if (await this.groupStore.existsWithName(group.name)) {
throw new NameExistsError('Group name already exists'); throw new NameExistsError('Group name already exists');
} }
@ -205,18 +205,18 @@ export class GroupService {
allUsers: IUser[], allUsers: IUser[],
): IGroupModel { ): IGroupModel {
const groupUsers = allGroupUsers.filter( const groupUsers = allGroupUsers.filter(
(user) => user.groupId == group.id, (user) => user.groupId === group.id,
); );
const groupUsersId = groupUsers.map((user) => user.userId); const groupUsersId = groupUsers.map((user) => user.userId);
const selectedUsers = allUsers.filter((user) => const selectedUsers = allUsers.filter((user) =>
groupUsersId.includes(user.id), groupUsersId.includes(user.id),
); );
const finalUsers = selectedUsers.map((user) => { const finalUsers = selectedUsers.map((user) => {
const roleUser = groupUsers.find((gu) => gu.userId == user.id); const roleUser = groupUsers.find((gu) => gu.userId === user.id);
return { return {
user: user, user: user,
joinedAt: roleUser.joinedAt, joinedAt: roleUser?.joinedAt,
createdBy: roleUser.createdBy, createdBy: roleUser?.createdBy,
}; };
}); });
return { ...group, users: finalUsers }; return { ...group, users: finalUsers };
@ -228,7 +228,7 @@ export class GroupService {
createdBy?: string, createdBy?: string,
): Promise<void> { ): Promise<void> {
if (Array.isArray(externalGroups)) { if (Array.isArray(externalGroups)) {
let newGroups = await this.groupStore.getNewGroupsForExternalUser( const newGroups = await this.groupStore.getNewGroupsForExternalUser(
userId, userId,
externalGroups, externalGroups,
); );
@ -237,7 +237,7 @@ export class GroupService {
newGroups.map((g) => g.id), newGroups.map((g) => g.id),
createdBy, createdBy,
); );
let oldGroups = await this.groupStore.getOldGroupsForExternalUser( const oldGroups = await this.groupStore.getOldGroupsForExternalUser(
userId, userId,
externalGroups, externalGroups,
); );

View File

@ -58,7 +58,7 @@ export class OpenApiService {
useErrorHandler(app: Express): void { useErrorHandler(app: Express): void {
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
if (err && err.status && err.validationErrors) { if (err?.status && err.validationErrors) {
const apiError = fromOpenApiValidationErrors( const apiError = fromOpenApiValidationErrors(
req.body, req.body,
err.validationErrors, err.validationErrors,

View File

@ -756,7 +756,9 @@ export default class ProjectService {
projectId, projectId,
); );
const groups = await this.groupService.getProjectGroups(projectId); const groups = await this.groupService.getProjectGroups(projectId);
const roleGroups = groups.filter((g) => g.roleId == currentRole.id); const roleGroups = groups.filter(
(g) => g.roleId === currentRole.id,
);
if (users.length + roleGroups.length < 2) { if (users.length + roleGroups.length < 2) {
throw new ProjectWithoutOwnerError(); throw new ProjectWithoutOwnerError();
} }

View File

@ -141,7 +141,7 @@ export class ProxyService {
} }
async deleteClientForProxyToken(secret: string): Promise<void> { async deleteClientForProxyToken(secret: string): Promise<void> {
let clientPromise = this.clients.get(secret); const clientPromise = this.clients.get(secret);
if (clientPromise) { if (clientPromise) {
const client = await clientPromise; const client = await clientPromise;
client.destroy(); client.destroy();

View File

@ -153,7 +153,7 @@ export default class StateService {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
moveVariantsToFeatureEnvironments(data: any) { moveVariantsToFeatureEnvironments(data: any) {
data.featureEnvironments?.forEach((featureEnvironment) => { data.featureEnvironments?.forEach((featureEnvironment) => {
let feature = data.features?.find( const feature = data.features?.find(
(f) => f.name === featureEnvironment.featureName, (f) => f.name === featureEnvironment.featureName,
); );
if (feature) { if (feature) {
@ -634,7 +634,7 @@ export default class StateService {
async importTagTypes( async importTagTypes(
tagTypes: ITagType[], tagTypes: ITagType[],
keepExisting: boolean, keepExisting: boolean,
oldTagTypes: ITagType[] = [], // eslint-disable-line oldTagTypes: ITagType[],
userName: string, userName: string,
): Promise<void> { ): Promise<void> {
const tagTypesToInsert = tagTypes.filter((tagType) => const tagTypesToInsert = tagTypes.filter((tagType) =>

View File

@ -17,19 +17,16 @@ export const parseFile: (file: string, data: string) => any = (
export const filterExisting: ( export const filterExisting: (
keepExisting: boolean, keepExisting: boolean,
existingArray: any[], existingArray: any[],
) => (item: any) => boolean = ) => (item: any) => boolean = (keepExisting, existingArray = []) => (item) => {
(keepExisting, existingArray = []) => if (keepExisting) {
(item) => { const found = existingArray.find((t) => t.name === item.name);
if (keepExisting) { return !found;
const found = existingArray.find((t) => t.name === item.name); }
return !found; return true;
} };
return true;
};
export const filterEqual: (existingArray: any[]) => (item: any) => boolean = export const filterEqual: (existingArray: any[]) => (item: any) => boolean =
(existingArray = []) => (existingArray = []) => (item) => {
(item) => {
const toggle = existingArray.find((t) => t.name === item.name); const toggle = existingArray.find((t) => t.name === item.name);
if (toggle) { if (toggle) {
return JSON.stringify(toggle) !== JSON.stringify(item); return JSON.stringify(toggle) !== JSON.stringify(item);

View File

@ -68,7 +68,7 @@ export default class TagTypeService {
async validate(tagType: Partial<ITagType> | undefined): Promise<void> { async validate(tagType: Partial<ITagType> | undefined): Promise<void> {
await tagTypeSchema.validateAsync(tagType); await tagTypeSchema.validateAsync(tagType);
if (tagType && tagType.name) { if (tagType?.name) {
await this.validateUnique(tagType.name); await this.validateUnique(tagType.name);
} }
} }

View File

@ -104,7 +104,7 @@ class UserService {
this.emailService = services.emailService; this.emailService = services.emailService;
this.sessionService = services.sessionService; this.sessionService = services.sessionService;
this.settingService = services.settingService; this.settingService = services.settingService;
if (authentication && authentication.createAdminUser) { if (authentication?.createAdminUser) {
process.nextTick(() => this.initAdminUser()); process.nextTick(() => this.initAdminUser());
} }

View File

@ -2,7 +2,7 @@ export const findDuplicates = <T>(arr: T[]): T[] => {
const seen: Set<T> = new Set(); const seen: Set<T> = new Set();
const duplicates: Set<T> = new Set(); const duplicates: Set<T> = new Set();
for (let item of arr) { for (const item of arr) {
if (seen.has(item)) { if (seen.has(item)) {
duplicates.add(item); duplicates.add(item);
} else { } else {

View File

@ -11,9 +11,15 @@ export const urlFriendlyString = (): Arbitrary<string> =>
fc fc
.array( .array(
fc.oneof( fc.oneof(
fc.integer({ min: 0x30, max: 0x39 }).map(String.fromCharCode), // numbers fc
fc.integer({ min: 0x41, max: 0x5a }).map(String.fromCharCode), // UPPERCASE LETTERS .integer({ min: 0x30, max: 0x39 })
fc.integer({ min: 0x61, max: 0x7a }).map(String.fromCharCode), // lowercase letters .map(String.fromCharCode), // numbers
fc
.integer({ min: 0x41, max: 0x5a })
.map(String.fromCharCode), // UPPERCASE LETTERS
fc
.integer({ min: 0x61, max: 0x7a })
.map(String.fromCharCode), // lowercase letters
fc.constantFrom('-', '_', '~', '.'), // rest fc.constantFrom('-', '_', '~', '.'), // rest
fc.lorem({ maxCount: 1 }), // random words for more 'realistic' names fc.lorem({ maxCount: 1 }), // random words for more 'realistic' names
), ),

View File

@ -24,7 +24,7 @@ const createFeature = async (featureName: string) => {
const loginRegularUser = () => const loginRegularUser = () =>
app.request app.request
.post(`/auth/demo/login`) .post('/auth/demo/login')
.send({ .send({
email: `${regularUserName}@getunleash.io`, email: `${regularUserName}@getunleash.io`,
}) })
@ -74,7 +74,7 @@ const getProject = async (projectName = 'default') => {
const getProjects = async () => { const getProjects = async () => {
return app.request return app.request
.get(`/api/admin/projects`) .get('/api/admin/projects')
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.expect(200); .expect(200);
}; };
@ -121,7 +121,7 @@ test('should be favorited in project endpoint', async () => {
await favoriteProject(); await favoriteProject();
const { body } = await app.request const { body } = await app.request
.get(`/api/admin/projects/default`) .get('/api/admin/projects/default')
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.expect(200); .expect(200);
@ -157,7 +157,7 @@ test('should be favorited in admin endpoint', async () => {
await favoriteFeature(featureName); await favoriteFeature(featureName);
const { body } = await app.request const { body } = await app.request
.get(`/api/admin/features`) .get('/api/admin/features')
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.expect(200); .expect(200);

View File

@ -44,7 +44,7 @@ describe('updating lifetimes', () => {
'it updates to the lifetime correctly: `%s`', 'it updates to the lifetime correctly: `%s`',
async (lifetimeDays) => { async (lifetimeDays) => {
const { body } = await app.request const { body } = await app.request
.put(`/api/admin/feature-types/release/lifetime`) .put('/api/admin/feature-types/release/lifetime')
.send({ lifetimeDays }) .send({ lifetimeDays })
.expect(200); .expect(200);
@ -53,7 +53,7 @@ describe('updating lifetimes', () => {
); );
test("if the feature type doesn't exist, you get a 404", async () => { test("if the feature type doesn't exist, you get a 404", async () => {
await app.request await app.request
.put(`/api/admin/feature-types/bogus-feature-type/lifetime`) .put('/api/admin/feature-types/bogus-feature-type/lifetime')
.send({ lifetimeDays: 45 }) .send({ lifetimeDays: 45 })
.expect(404); .expect(404);
}); });
@ -72,7 +72,7 @@ describe('updating lifetimes', () => {
test('the :id parameter is not case sensitive', async () => { test('the :id parameter is not case sensitive', async () => {
const lifetimeDays = 45; const lifetimeDays = 45;
const { body } = await app.request const { body } = await app.request
.put(`/api/admin/feature-types/kIlL-SwItCh/lifetime`) .put('/api/admin/feature-types/kIlL-SwItCh/lifetime')
.send({ lifetimeDays }) .send({ lifetimeDays })
.expect(200); .expect(200);

View File

@ -318,8 +318,8 @@ test('Update update_at when setHealth runs', async () => {
.expect(200) .expect(200)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect((res) => { .expect((res) => {
let now = new Date().getTime(); const now = new Date().getTime();
let updatedAt = new Date(res.body.updatedAt).getTime(); const updatedAt = new Date(res.body.updatedAt).getTime();
expect(now - updatedAt).toBeLessThan(5000); expect(now - updatedAt).toBeLessThan(5000);
}); });
}); });

View File

@ -88,7 +88,7 @@ test('Trying to do operations on a non-existing feature yields 404', async () =>
weight: 700, weight: 700,
weightType: WeightType.VARIABLE, weightType: WeightType.VARIABLE,
}); });
let patch = jsonpatch.generate(observer); const patch = jsonpatch.generate(observer);
await app.request await app.request
.patch('/api/admin/projects/default/features/${featureName}/variants') .patch('/api/admin/projects/default/features/${featureName}/variants')
.send(patch) .send(patch)
@ -744,7 +744,7 @@ test('Patching with a fixed variant and variable variants splits remaining weigh
.get(`/api/admin/projects/default/features/${featureName}/variants`) .get(`/api/admin/projects/default/features/${featureName}/variants`)
.expect(200) .expect(200)
.expect((res) => { .expect((res) => {
let body = res.body; const body = res.body;
expect(body.variants).toHaveLength(7); expect(body.variants).toHaveLength(7);
expect( expect(
body.variants.reduce((total, v) => total + v.weight, 0), body.variants.reduce((total, v) => total + v.weight, 0),
@ -817,7 +817,7 @@ test('Multiple fixed variants gets added together to decide how much weight vari
.get(`/api/admin/projects/default/features/${featureName}/variants`) .get(`/api/admin/projects/default/features/${featureName}/variants`)
.expect(200) .expect(200)
.expect((res) => { .expect((res) => {
let body = res.body; const body = res.body;
expect(body.variants).toHaveLength(3); expect(body.variants).toHaveLength(3);
expect( expect(
body.variants.find((v) => v.name === 'variant3').weight, body.variants.find((v) => v.name === 'variant3').weight,
@ -921,7 +921,7 @@ test('If sum of fixed variant weight equals 1000 variable variants gets weight 0
.get(`/api/admin/projects/default/features/${featureName}/variants`) .get(`/api/admin/projects/default/features/${featureName}/variants`)
.expect(200) .expect(200)
.expect((res) => { .expect((res) => {
let body = res.body; const body = res.body;
expect(body.variants).toHaveLength(4); expect(body.variants).toHaveLength(4);
expect( expect(
body.variants.find((v) => v.name === 'variant3').weight, body.variants.find((v) => v.name === 'variant3').weight,

View File

@ -25,7 +25,7 @@ afterAll(async () => {
}); });
const expireAt = (addDays: number = 7): Date => { const expireAt = (addDays: number = 7): Date => {
let now = new Date(); const now = new Date();
now.setDate(now.getDate() + addDays); now.setDate(now.getDate() + addDays);
return now; return now;
}; };

View File

@ -414,10 +414,10 @@ test(`should not show environment on feature toggle, when environment is disable
.expect(200); .expect(200);
const result = body.environments; const result = body.environments;
let dev = result.find((e) => e.name === 'development'); const dev = result.find((e) => e.name === 'development');
expect(dev).toBeTruthy(); expect(dev).toBeTruthy();
expect(dev.enabled).toBe(true); expect(dev.enabled).toBe(true);
let prod = result.find((e) => e.name === 'production'); const prod = result.find((e) => e.name === 'production');
expect(prod).toBeTruthy(); expect(prod).toBeTruthy();
expect(prod.enabled).toBe(false); expect(prod.enabled).toBe(false);
}); });

View File

@ -380,7 +380,7 @@ test('generates USER_UPDATED event', async () => {
}); });
test('Anonymises name, username and email fields if anonymiseEventLog flag is set', async () => { test('Anonymises name, username and email fields if anonymiseEventLog flag is set', async () => {
let anonymisedApp = await setupAppWithCustomConfig( const anonymisedApp = await setupAppWithCustomConfig(
stores, stores,
{ experimental: { flags: { anonymiseEventLog: true } } }, { experimental: { flags: { anonymiseEventLog: true } } },
db, db,
@ -393,10 +393,10 @@ test('Anonymises name, username and email fields if anonymiseEventLog flag is se
rootRole: editorRole.id, rootRole: editorRole.id,
}) })
.set('Content-Type', 'application/json'); .set('Content-Type', 'application/json');
let response = await anonymisedApp.request.get( const response = await anonymisedApp.request.get(
'/api/admin/user-admin/access', '/api/admin/user-admin/access',
); );
let body = response.body; const body = response.body;
expect(body.users[0].email).toEqual('aeb83743e@unleash.run'); expect(body.users[0].email).toEqual('aeb83743e@unleash.run');
expect(body.users[0].name).toEqual('3a8b17647@unleash.run'); expect(body.users[0].name).toEqual('3a8b17647@unleash.run');
expect(body.users[0].username).toEqual(''); // Not set, so anonymise should return the empty string. expect(body.users[0].username).toEqual(''); // Not set, so anonymise should return the empty string.

View File

@ -9,7 +9,7 @@ let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
let patStore: IPatStore; let patStore: IPatStore;
let tomorrow = new Date(); const tomorrow = new Date();
let firstSecret; let firstSecret;
let firstId; let firstId;
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 1);
@ -149,7 +149,7 @@ test('should get only current user PATs', async () => {
test('should fail creation of PAT with passed expiry', async () => { test('should fail creation of PAT with passed expiry', async () => {
const { request } = app; const { request } = app;
let yesterday = new Date(); const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
await request await request
.post('/api/admin/user/tokens') .post('/api/admin/user/tokens')

View File

@ -46,13 +46,15 @@ test('multiple slashes after base path is also rejected with 404', async () => {
await appWithBaseUrl.request.get('/demo/api/client/features').expect(401); await appWithBaseUrl.request.get('/demo/api/client/features').expect(401);
}); });
test(`Access with API token is granted`, async () => { test('Access with API token is granted', async () => {
let token = await app.services.apiTokenService.createApiTokenWithProjects({ const token = await app.services.apiTokenService.createApiTokenWithProjects(
environment: 'default', {
projects: ['default'], environment: 'default',
tokenName: 'test', projects: ['default'],
type: ApiTokenType.CLIENT, tokenName: 'test',
}); type: ApiTokenType.CLIENT,
},
);
await app.request await app.request
.get('/api/client/features') .get('/api/client/features')
.set('Authorization', token.secret) .set('Authorization', token.secret)

View File

@ -77,7 +77,7 @@ test('Can log in', async () => {
}); });
test('Gets rate limited after 10 tries', async () => { test('Gets rate limited after 10 tries', async () => {
for (let statusCode of [...Array(10).fill(200), 429]) { for (const statusCode of [...Array(10).fill(200), 429]) {
await app.request await app.request
.post('/auth/simple/login') .post('/auth/simple/login')
.send({ .send({

View File

@ -380,8 +380,7 @@ test('should send all segments that are in use by feature', async () => {
const globalSegmentIds = globalSegments.map((segment) => segment.id); const globalSegmentIds = globalSegments.map((segment) => segment.id);
const allSegmentIds = clientFeatures.features const allSegmentIds = clientFeatures.features
.map((feat) => feat.strategies.map((strategy) => strategy.segments)) .flatMap((feat) => feat.strategies.map((strategy) => strategy.segments))
.flat()
.flat() .flat()
.filter((x) => !!x); .filter((x) => !!x);
const toggleSegmentIds = [...new Set(allSegmentIds)]; const toggleSegmentIds = [...new Set(allSegmentIds)];

View File

@ -149,8 +149,7 @@ test('all tags are listed in the root "tags" list', async () => {
// store other invalid tags that already exist on this // store other invalid tags that already exist on this
// operation // operation
const preExistingTags = const preExistingTags =
(invalidTags[path] ?? {})[operation]?.invalidTags ?? []; invalidTags[path]?.[operation]?.invalidTags ?? [];
// add information about the invalid tag to the invalid tags // add information about the invalid tag to the invalid tags
// dict. // dict.
invalidTags = { invalidTags = {

View File

@ -9,7 +9,7 @@ let db: ITestDb;
let appErrorLogs: string[] = []; let appErrorLogs: string[] = [];
beforeAll(async () => { beforeAll(async () => {
db = await dbInit(`proxy_concurrency`, getLogger); db = await dbInit('proxy_concurrency', getLogger);
const baseLogger = getLogger(); const baseLogger = getLogger();
const appLogger = { const appLogger = {
...baseLogger, ...baseLogger,

View File

@ -34,7 +34,9 @@ async function resetDatabase(knex) {
knex.table('tag_types').del(), knex.table('tag_types').del(),
knex.table('addons').del(), knex.table('addons').del(),
knex.table('users').del(), knex.table('users').del(),
knex.table('reset_tokens').del(), knex
.table('reset_tokens')
.del(),
// knex.table('settings').del(), // knex.table('settings').del(),
]); ]);
} }
@ -80,7 +82,7 @@ export interface ITestDb {
} }
export default async function init( export default async function init(
databaseSchema: string = 'test', databaseSchema = 'test',
getLogger: LogProvider = noLoggerProvider, getLogger: LogProvider = noLoggerProvider,
configOverride: Partial<IUnleashOptions> = {}, configOverride: Partial<IUnleashOptions> = {},
): Promise<ITestDb> { ): Promise<ITestDb> {

View File

@ -55,7 +55,7 @@ const createUser = async (role?: number) => {
return user; return user;
}; };
let groupIndex = 0; const groupIndex = 0;
const createGroup = async ({ const createGroup = async ({
users, users,
role, role,
@ -1837,7 +1837,7 @@ test('access overview should have admin access and default project for admin use
const accessOverView: IUserAccessOverview[] = const accessOverView: IUserAccessOverview[] =
await accessService.getUserAccessOverview(); await accessService.getUserAccessOverview();
const userAccess = accessOverView.find( const userAccess = accessOverView.find(
(overviewRow) => overviewRow.userId == user.id, (overviewRow) => overviewRow.userId === user.id,
)!; )!;
expect(userAccess.userId).toBe(user.id); expect(userAccess.userId).toBe(user.id);
@ -1890,7 +1890,7 @@ test('access overview should have group access for groups that they are in', asy
const accessOverView: IUserAccessOverview[] = const accessOverView: IUserAccessOverview[] =
await accessService.getUserAccessOverview(); await accessService.getUserAccessOverview();
const userAccess = accessOverView.find( const userAccess = accessOverView.find(
(overviewRow) => overviewRow.userId == user.id, (overviewRow) => overviewRow.userId === user.id,
)!; )!;
expect(userAccess.userId).toBe(user.id); expect(userAccess.userId).toBe(user.id);

View File

@ -159,11 +159,11 @@ test('Setting an override disables all other envs', async () => {
const environments = await service.getAll(); const environments = await service.getAll();
const targetedEnvironment = environments.find( const targetedEnvironment = environments.find(
(env) => env.name == enabledEnvName, (env) => env.name === enabledEnvName,
); );
const allOtherEnvironments = environments const allOtherEnvironments = environments
.filter((x) => x.name != enabledEnvName) .filter((x) => x.name !== enabledEnvName)
.map((env) => env.enabled); .map((env) => env.enabled);
expect(targetedEnvironment?.enabled).toBe(true); expect(targetedEnvironment?.enabled).toBe(true);
@ -184,7 +184,7 @@ test('Passing an empty override does nothing', async () => {
const environments = await service.getAll(); const environments = await service.getAll();
const targetedEnvironment = environments.find( const targetedEnvironment = environments.find(
(env) => env.name == enabledEnvName, (env) => env.name === enabledEnvName,
); );
expect(targetedEnvironment?.enabled).toBe(true); expect(targetedEnvironment?.enabled).toBe(true);
@ -255,11 +255,11 @@ test('Override works correctly when enabling default and disabling prod and dev'
const environments = await service.getAll(); const environments = await service.getAll();
const targetedEnvironment = environments.find( const targetedEnvironment = environments.find(
(env) => env.name == defaultEnvironment, (env) => env.name === defaultEnvironment,
); );
const allOtherEnvironments = environments const allOtherEnvironments = environments
.filter((x) => x.name != defaultEnvironment) .filter((x) => x.name !== defaultEnvironment)
.map((env) => env.enabled); .map((env) => env.enabled);
const envNames = environments.map((x) => x.name); const envNames = environments.map((x) => x.name);

View File

@ -351,8 +351,8 @@ test('cloning a feature toggle copies variant environments correctly', async ()
newToggleName, newToggleName,
'default', 'default',
clonedToggleName, clonedToggleName,
true,
'test-user', 'test-user',
true,
); );
const clonedToggle = const clonedToggle =
@ -379,8 +379,8 @@ test('cloning a feature toggle not allowed for change requests enabled', async (
'newToggleName', 'newToggleName',
'default', 'default',
'clonedToggleName', 'clonedToggleName',
true,
'test-user', 'test-user',
true,
), ),
).rejects.toEqual( ).rejects.toEqual(
new ForbiddenError( new ForbiddenError(
@ -407,7 +407,7 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
const featureName = 'ToggleWithSegments'; const featureName = 'ToggleWithSegments';
const clonedFeatureName = 'AWholeNewFeatureToggle'; const clonedFeatureName = 'AWholeNewFeatureToggle';
let segment = await segmentService.create( const segment = await segmentService.create(
{ {
name: 'SomeSegment', name: 'SomeSegment',
constraints: mockConstraints(), constraints: mockConstraints(),
@ -442,11 +442,13 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
featureName, featureName,
'default', 'default',
clonedFeatureName, clonedFeatureName,
true,
'test-user', 'test-user',
true,
); );
let feature = await service.getFeature({ featureName: clonedFeatureName }); const feature = await service.getFeature({
featureName: clonedFeatureName,
});
expect( expect(
feature.environments.find((x) => x.name === 'default')?.strategies[0] feature.environments.find((x) => x.name === 'default')?.strategies[0]
.segments, .segments,

View File

@ -940,7 +940,7 @@ test('should change a users role in the project', async () => {
await projectService.addUser(project.id, member.id, projectUser.id, 'test'); await projectService.addUser(project.id, member.id, projectUser.id, 'test');
const { users } = await projectService.getAccessToProject(project.id); const { users } = await projectService.getAccessToProject(project.id);
let memberUser = users.filter((u) => u.roleId === member.id); const memberUser = users.filter((u) => u.roleId === member.id);
expect(memberUser).toHaveLength(1); expect(memberUser).toHaveLength(1);
expect(memberUser[0].id).toBe(projectUser.id); expect(memberUser[0].id).toBe(projectUser.id);
@ -958,7 +958,7 @@ test('should change a users role in the project', async () => {
'test', 'test',
); );
let { users: updatedUsers } = await projectService.getAccessToProject( const { users: updatedUsers } = await projectService.getAccessToProject(
project.id, project.id,
); );
const customUser = updatedUsers.filter((u) => u.roleId === customRole.id); const customUser = updatedUsers.filter((u) => u.roleId === customRole.id);

View File

@ -141,7 +141,7 @@ test('Should import variants from old format and convert to new format (per envi
keepExisting: false, keepExisting: false,
dropBeforeImport: true, dropBeforeImport: true,
}); });
let featureEnvironments = await stores.featureEnvironmentStore.getAll(); const featureEnvironments = await stores.featureEnvironmentStore.getAll();
expect(featureEnvironments).toHaveLength(6); // There are 3 environments enabled and 2 features expect(featureEnvironments).toHaveLength(6); // There are 3 environments enabled and 2 features
expect( expect(
featureEnvironments featureEnvironments
@ -155,13 +155,13 @@ test('Should import variants in new format (per environment)', async () => {
keepExisting: false, keepExisting: false,
dropBeforeImport: true, dropBeforeImport: true,
}); });
let exportedJson = await stateService.export({}); const exportedJson = await stateService.export({});
await stateService.import({ await stateService.import({
data: exportedJson, data: exportedJson,
keepExisting: false, keepExisting: false,
dropBeforeImport: true, dropBeforeImport: true,
}); });
let featureEnvironments = await stores.featureEnvironmentStore.getAll(); const featureEnvironments = await stores.featureEnvironmentStore.getAll();
expect(featureEnvironments).toHaveLength(6); // 3 environments, 2 features === 6 rows expect(featureEnvironments).toHaveLength(6); // 3 environments, 2 features === 6 rows
}); });

View File

@ -120,7 +120,7 @@ test('should not be able to login with deleted user', async () => {
userService.loginUser('deleted_user', 'unleash4all'), userService.loginUser('deleted_user', 'unleash4all'),
).rejects.toThrow( ).rejects.toThrow(
new PasswordMismatch( new PasswordMismatch(
`The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.`, 'The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.',
), ),
); );
}); });
@ -139,7 +139,7 @@ test('should not be able to login without password_hash on user', async () => {
userService.loginUser('deleted_user', 'anything-should-fail'), userService.loginUser('deleted_user', 'anything-should-fail'),
).rejects.toThrow( ).rejects.toThrow(
new PasswordMismatch( new PasswordMismatch(
`The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.`, 'The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.',
), ),
); );
}); });
@ -273,7 +273,7 @@ test('should throw if rootRole is wrong via SSO', async () => {
name: 'some', name: 'some',
autoCreate: true, autoCreate: true,
}), }),
).rejects.toThrow(new BadDataError(`Could not find rootRole=Member`)); ).rejects.toThrow(new BadDataError('Could not find rootRole=Member'));
}); });
test('should update user name when signing in via SSO', async () => { test('should update user name when signing in via SSO', async () => {
@ -330,5 +330,5 @@ test('should throw if autoCreate is false via SSO', async () => {
name: 'some', name: 'some',
autoCreate: false, autoCreate: false,
}), }),
).rejects.toThrow(new NotFoundError(`No user found`)); ).rejects.toThrow(new NotFoundError('No user found'));
}); });

View File

@ -192,7 +192,7 @@ test('Should get all events of type', async () => {
await Promise.all( await Promise.all(
[0, 1, 2, 3, 4, 5].map(async (id) => { [0, 1, 2, 3, 4, 5].map(async (id) => {
const event = const event =
id % 2 == 0 id % 2 === 0
? new FeatureCreatedEvent({ ? new FeatureCreatedEvent({
project: data.project, project: data.project,
featureName: data.name, featureName: data.name,

View File

@ -24,8 +24,8 @@ afterEach(async () => {
}); });
test('Setting enabled to same as existing value returns 0', async () => { test('Setting enabled to same as existing value returns 0', async () => {
let envName = 'enabled-to-true'; const envName = 'enabled-to-true';
let featureName = 'enabled-to-true-feature'; const featureName = 'enabled-to-true-feature';
await environmentStore.create({ await environmentStore.create({
name: envName, name: envName,
enabled: true, enabled: true,
@ -47,8 +47,8 @@ test('Setting enabled to same as existing value returns 0', async () => {
}); });
test('Setting enabled to not existing value returns 1', async () => { test('Setting enabled to not existing value returns 1', async () => {
let envName = 'enabled-toggle'; const envName = 'enabled-toggle';
let featureName = 'enabled-toggle-feature'; const featureName = 'enabled-toggle-feature';
await environmentStore.create({ await environmentStore.create({
name: envName, name: envName,
enabled: true, enabled: true,

View File

@ -112,6 +112,6 @@ test('should throw not found error if feature does not exist', async () => {
test('Returns empty tag list for existing feature with no tags', async () => { test('Returns empty tag list for existing feature with no tags', async () => {
const name = 'feature.with.no.tags'; const name = 'feature.with.no.tags';
await featureToggleStore.create('default', { name }); await featureToggleStore.create('default', { name });
let tags = await featureTagStore.getAllTagsForFeature(name); const tags = await featureTagStore.getAllTagsForFeature(name);
expect(tags).toHaveLength(0); expect(tags).toHaveLength(0);
}); });

View File

@ -11,7 +11,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
environments: IEnvironment[] = []; environments: IEnvironment[] = [];
disable(environments: IEnvironment[]): Promise<void> { disable(environments: IEnvironment[]): Promise<void> {
for (let env of this.environments) { for (const env of this.environments) {
if (environments.map((e) => e.name).includes(env.name)) if (environments.map((e) => e.name).includes(env.name))
env.enabled = false; env.enabled = false;
} }
@ -19,7 +19,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
} }
enable(environments: IEnvironment[]): Promise<void> { enable(environments: IEnvironment[]): Promise<void> {
for (let env of this.environments) { for (const env of this.environments) {
if (environments.map((e) => e.name).includes(env.name)) if (environments.map((e) => e.name).includes(env.name))
env.enabled = true; env.enabled = true;
} }

View File

@ -2,9 +2,7 @@ import { IFavoriteFeaturesStore } from '../../lib/types';
import { IFavoriteFeatureKey } from '../../lib/types/stores/favorite-features'; import { IFavoriteFeatureKey } from '../../lib/types/stores/favorite-features';
import { IFavoriteFeature } from '../../lib/types/favorites'; import { IFavoriteFeature } from '../../lib/types/favorites';
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
export default class FakeFavoriteFeaturesStore export default class FakeFavoriteFeaturesStore implements IFavoriteFeaturesStore {
implements IFavoriteFeaturesStore
{
addFavoriteFeature( addFavoriteFeature(
favorite: IFavoriteFeatureKey, favorite: IFavoriteFeatureKey,
): Promise<IFavoriteFeature> { ): Promise<IFavoriteFeature> {

View File

@ -2,9 +2,7 @@ import { IFavoriteProjectsStore } from '../../lib/types';
import { IFavoriteProjectKey } from '../../lib/types/stores/favorite-projects'; import { IFavoriteProjectKey } from '../../lib/types/stores/favorite-projects';
import { IFavoriteProject } from '../../lib/types/favorites'; import { IFavoriteProject } from '../../lib/types/favorites';
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
export default class FakeFavoriteProjectsStore export default class FakeFavoriteProjectsStore implements IFavoriteProjectsStore {
implements IFavoriteProjectsStore
{
addFavoriteProject( addFavoriteProject(
favorite: IFavoriteProjectKey, favorite: IFavoriteProjectKey,
): Promise<IFavoriteProject> { ): Promise<IFavoriteProject> {

View File

@ -41,7 +41,9 @@ export default class FakeFeatureEnvironmentStore
fe.featureName === featureName && fe.featureName === featureName &&
environments.includes(fe.environment), environments.includes(fe.environment),
) )
.map((fe) => (fe.variants = variants)); .forEach((fe) => {
fe.variants = variants;
});
} }
async delete(key: FeatureEnvironmentKey): Promise<void> { async delete(key: FeatureEnvironmentKey): Promise<void> {

View File

@ -198,8 +198,8 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
} }
async getAllVariants(): Promise<IFeatureEnvironment[]> { async getAllVariants(): Promise<IFeatureEnvironment[]> {
let features = await this.getAll(); const features = await this.getAll();
let variants = features.flatMap((feature) => ({ const variants = features.flatMap((feature) => ({
featureName: feature.name, featureName: feature.name,
environment: 'development', environment: 'development',
variants: feature.variants, variants: feature.variants,
@ -271,7 +271,9 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
} }
dropAllVariants(): Promise<void> { dropAllVariants(): Promise<void> {
this.features.forEach((feature) => (feature.variants = [])); this.features.forEach((feature) => {
feature.variants = [];
});
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -60,7 +60,7 @@ export default class FakeRoleStore implements IRoleStore {
} }
async getRoleByName(name: string): Promise<IRole> { async getRoleByName(name: string): Promise<IRole> {
return this.roles.find((r) => (r.name = name)) as IRole; return this.roles.find((r) => r.name === name) as IRole;
} }
getRolesForProject(projectId: string): Promise<IRole[]> { getRolesForProject(projectId: string): Promise<IRole[]> {

1272
yarn.lock

File diff suppressed because it is too large Load Diff