1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-26 01:17:00 +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",
"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",
"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",
"prebuild:watch": "yarn run clean",
"build:watch": "tsc -w --strictNullChecks false",
@ -168,6 +168,7 @@
"@apidevtools/swagger-parser": "10.1.0",
"@babel/core": "7.22.17",
"@swc/core": "1.3.88",
"@biomejs/biome": "1.2.2",
"@swc/jest": "0.2.29",
"@types/bcryptjs": "2.4.3",
"@types/cors": "2.8.14",
@ -190,20 +191,10 @@
"@types/supertest": "2.0.12",
"@types/type-is": "1.6.4",
"@types/uuid": "9.0.3",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"concurrently": "^8.0.1",
"copyfiles": "2.4.1",
"coveralls": "3.1.1",
"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",
"fast-check": "3.13.0",
"fetch-mock": "9.11.0",
@ -213,7 +204,6 @@
"lint-staged": "13.2.3",
"nock": "13.3.3",
"openapi-enforcer": "1.22.3",
"prettier": "2.8.1",
"proxyquire": "2.1.3",
"source-map-support": "0.5.21",
"superagent": "8.1.2",
@ -241,24 +231,10 @@
},
"lint-staged": {
"*.{js,ts}": [
"eslint --fix"
"biome check --apply"
],
"*.{json,yaml,md}": [
"prettier --write"
]
},
"prettier": {
"proseWrap": "never",
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "all",
"overrides": [
{
"files": "*.{json,yaml,yml,md}",
"options": {
"tabWidth": 2
}
}
"biome format --write"
]
}
}

View File

@ -9,10 +9,10 @@ function isPrerelease(version) {
return arr && arr.length > 0;
}
if(isPrerelease(version)){
console.log('beta')
}else if(semver.gt(version, latestUnleashVersion)) {
if (isPrerelease(version)) {
console.log('beta');
} else if (semver.gt(version, latestUnleashVersion)) {
console.log('latest');
} else {
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();
});
test(`Should call datadog webhook for toggled environment`, async () => {
test('Should call datadog webhook for toggled environment', async () => {
const addon = new DatadogAddon({
getLogger: noLogger,
unleashUrl: 'http://some-url.com',
@ -169,7 +169,7 @@ test(`Should call datadog webhook for toggled environment`, async () => {
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({
getLogger: noLogger,
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();
});
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({
getLogger: noLogger,
unleashUrl: 'http://some-url.com',

View File

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

View File

@ -315,8 +315,7 @@ const testCases: [string, IEvent, string][] = [
].map(
([operator, display]) =>
<[string, IEvent, string]>[
'when default strategy updated with numeric constraint ' +
operator,
`when default strategy updated with numeric constraint ${operator}`,
{
id: 39,
type: FEATURE_STRATEGY_UPDATE,
@ -504,7 +503,7 @@ const testCases: [string, IEvent, string][] = [
];
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 actual = formatter.format(event);
expect(actual).toBe(expected);

View File

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

View File

@ -41,7 +41,7 @@ export default async function getApp(
const baseUriPath = config.server.baseUriPath || '';
const publicFolder = config.publicFolder || findPublicFolder();
let indexHTML = await loadIndexHTML(config, publicFolder);
const indexHTML = await loadIndexHTML(config, publicFolder);
app.set('trust proxy', true);
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 () => {
let config = createConfig({});
const config = createConfig({});
expect(config.server.baseUriPath).toBe('');
});
test('BASE_URI_PATH defined in env is passed through', async () => {
process.env.BASE_URI_PATH = '/demo';
let config = createConfig({});
const config = createConfig({});
expect(config.server.baseUriPath).toBe('/demo');
delete process.env.BASE_URI_PATH;
});
test('environment variable takes precedence over configured variable', async () => {
process.env.BASE_URI_PATH = '/demo';
let config = createConfig({
const config = createConfig({
server: {
baseUriPath: '/other',
},
@ -463,7 +463,7 @@ test('environment variable takes precedence over configured variable', async ()
test.each(['demo', '/demo', '/demo/'])(
'Trailing and leading slashes gets normalized for base path %s',
async (path) => {
let config = createConfig({
const config = createConfig({
server: {
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 () => {
let config = createConfig({
const config = createConfig({
enterpriseVersion: '5.3.0',
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 () => {
let config = createConfig({
const config = createConfig({
enterpriseVersion: '5.3.0',
ui: { environment: 'Enterprise' },
});

View File

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

View File

@ -848,8 +848,7 @@ export class AccessStore implements IAccessStore {
}
async getUserAccessOverview(): Promise<IUserAccessOverview[]> {
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
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
FROM users u, LATERAL (
SELECT ARRAY (
SELECT ru.project

View File

@ -104,9 +104,7 @@ const remapUsageRow = (input) => {
};
};
export default class ClientApplicationsStore
implements IClientApplicationsStore
{
export default class ClientApplicationsStore implements IClientApplicationsStore {
private db: Db;
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.
async batchInsertMetrics(metrics: IClientMetricsEnv[]): Promise<void> {
if (!metrics || metrics.length == 0) {
if (!metrics || metrics.length === 0) {
return;
}
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']);
const store = new EventStore(db.rawDatabase, getLogger);
let events = await store.setUnannouncedToAnnounced();
const events = await store.setUnannouncedToAnnounced();
expect(events).toBeTruthy();
expect(events.length).toBe(505);
await db.destroy();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@ import { Application } from 'express';
import NoAuthUser from '../types/no-auth-user';
// eslint-disable-next-line
function noneAuthentication(basePath = '', app: Application): void {
app.use(`${basePath}/api/admin/`, (req, res, next) => {
function noneAuthentication(basePath: string, app: Application): void {
app.use(`${basePath || ''}/api/admin/`, (req, res, next) => {
// @ts-expect-error
if (!req.user) {
// @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 () => {
let fakeLogger = {
const fakeLogger = {
debug: () => {},
info: () => {},
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');
}),
};
let mw = patMiddleware(conf, { accountService });
const mw = patMiddleware(conf, { accountService });
const cb = jest.fn();
const req = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,7 @@ describe('Public Signup API', () => {
destroy();
});
const expireAt = (addDays: number = 7): Date => {
let now = new Date();
const now = new Date();
now.setDate(now.getDate() + addDays);
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 () => {
let token = await createApiToken.validateAsync({
const token = await createApiToken.validateAsync({
username: 'test',
type: 'admin',
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 () => {
let token = await createApiToken.validateAsync({
const token = await createApiToken.validateAsync({
username: 'test',
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 () => {
let token = await createApiToken.validateAsync({
const token = await createApiToken.validateAsync({
username: 'test',
type: 'admin',
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 () => {
let token = await createApiToken.validateAsync({
const token = await createApiToken.validateAsync({
username: 'test',
type: 'frontend',
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 () => {
let token = await createApiToken.validateAsync({
const token = await createApiToken.validateAsync({
username: 'test',
type: 'frontend',
project: 'default',

View File

@ -113,5 +113,5 @@ test('should shutdown the server when calling stop()', async () => {
createTestConfig({ server: { port: 0 } }),
);
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 users = await this.accountStore.getAllWithId(userIdList);
return users.map((user) => {
const role = userRoleList.find((r) => r.userId == user.id)!;
const role = userRoleList.find((r) => r.userId === user.id)!;
return {
...user,
addedAt: role.addedAt!,

View File

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

View File

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

View File

@ -81,7 +81,7 @@ export class ApiTokenService {
try {
this.activeTokens = await this.getAllActiveTokens();
} 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;
}
}
@ -259,7 +259,7 @@ export class ApiTokenService {
if (!errorDetails) {
return 'invalid';
}
let invalidProject = projects.find((project) => {
const invalidProject = projects.find((project) => {
return errorDetails.includes(`=(${project})`);
});
return invalidProject || 'invalid';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -756,7 +756,9 @@ export default class ProjectService {
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) {
throw new ProjectWithoutOwnerError();
}

View File

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

View File

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

View File

@ -17,19 +17,16 @@ export const parseFile: (file: string, data: string) => any = (
export const filterExisting: (
keepExisting: boolean,
existingArray: any[],
) => (item: any) => boolean =
(keepExisting, existingArray = []) =>
(item) => {
if (keepExisting) {
const found = existingArray.find((t) => t.name === item.name);
return !found;
}
return true;
};
) => (item: any) => boolean = (keepExisting, existingArray = []) => (item) => {
if (keepExisting) {
const found = existingArray.find((t) => t.name === item.name);
return !found;
}
return true;
};
export const filterEqual: (existingArray: any[]) => (item: any) => boolean =
(existingArray = []) =>
(item) => {
(existingArray = []) => (item) => {
const toggle = existingArray.find((t) => t.name === item.name);
if (toggle) {
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> {
await tagTypeSchema.validateAsync(tagType);
if (tagType && tagType.name) {
if (tagType?.name) {
await this.validateUnique(tagType.name);
}
}

View File

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

View File

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

View File

@ -11,9 +11,15 @@ export const urlFriendlyString = (): Arbitrary<string> =>
fc
.array(
fc.oneof(
fc.integer({ min: 0x30, max: 0x39 }).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
.integer({ min: 0x30, max: 0x39 })
.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.lorem({ maxCount: 1 }), // random words for more 'realistic' names
),

View File

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

View File

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

View File

@ -318,8 +318,8 @@ test('Update update_at when setHealth runs', async () => {
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
let now = new Date().getTime();
let updatedAt = new Date(res.body.updatedAt).getTime();
const now = new Date().getTime();
const updatedAt = new Date(res.body.updatedAt).getTime();
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,
weightType: WeightType.VARIABLE,
});
let patch = jsonpatch.generate(observer);
const patch = jsonpatch.generate(observer);
await app.request
.patch('/api/admin/projects/default/features/${featureName}/variants')
.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`)
.expect(200)
.expect((res) => {
let body = res.body;
const body = res.body;
expect(body.variants).toHaveLength(7);
expect(
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`)
.expect(200)
.expect((res) => {
let body = res.body;
const body = res.body;
expect(body.variants).toHaveLength(3);
expect(
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`)
.expect(200)
.expect((res) => {
let body = res.body;
const body = res.body;
expect(body.variants).toHaveLength(4);
expect(
body.variants.find((v) => v.name === 'variant3').weight,

View File

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

View File

@ -414,10 +414,10 @@ test(`should not show environment on feature toggle, when environment is disable
.expect(200);
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.enabled).toBe(true);
let prod = result.find((e) => e.name === 'production');
const prod = result.find((e) => e.name === 'production');
expect(prod).toBeTruthy();
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 () => {
let anonymisedApp = await setupAppWithCustomConfig(
const anonymisedApp = await setupAppWithCustomConfig(
stores,
{ experimental: { flags: { anonymiseEventLog: true } } },
db,
@ -393,10 +393,10 @@ test('Anonymises name, username and email fields if anonymiseEventLog flag is se
rootRole: editorRole.id,
})
.set('Content-Type', 'application/json');
let response = await anonymisedApp.request.get(
const response = await anonymisedApp.request.get(
'/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].name).toEqual('3a8b17647@unleash.run');
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 patStore: IPatStore;
let tomorrow = new Date();
const tomorrow = new Date();
let firstSecret;
let firstId;
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 () => {
const { request } = app;
let yesterday = new Date();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
await request
.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);
});
test(`Access with API token is granted`, async () => {
let token = await app.services.apiTokenService.createApiTokenWithProjects({
environment: 'default',
projects: ['default'],
tokenName: 'test',
type: ApiTokenType.CLIENT,
});
test('Access with API token is granted', async () => {
const token = await app.services.apiTokenService.createApiTokenWithProjects(
{
environment: 'default',
projects: ['default'],
tokenName: 'test',
type: ApiTokenType.CLIENT,
},
);
await app.request
.get('/api/client/features')
.set('Authorization', token.secret)

View File

@ -77,7 +77,7 @@ test('Can log in', 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
.post('/auth/simple/login')
.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 allSegmentIds = clientFeatures.features
.map((feat) => feat.strategies.map((strategy) => strategy.segments))
.flat()
.flatMap((feat) => feat.strategies.map((strategy) => strategy.segments))
.flat()
.filter((x) => !!x);
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
// operation
const preExistingTags =
(invalidTags[path] ?? {})[operation]?.invalidTags ?? [];
invalidTags[path]?.[operation]?.invalidTags ?? [];
// add information about the invalid tag to the invalid tags
// dict.
invalidTags = {

View File

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

View File

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

View File

@ -55,7 +55,7 @@ const createUser = async (role?: number) => {
return user;
};
let groupIndex = 0;
const groupIndex = 0;
const createGroup = async ({
users,
role,
@ -1837,7 +1837,7 @@ test('access overview should have admin access and default project for admin use
const accessOverView: IUserAccessOverview[] =
await accessService.getUserAccessOverview();
const userAccess = accessOverView.find(
(overviewRow) => overviewRow.userId == user.id,
(overviewRow) => overviewRow.userId === 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[] =
await accessService.getUserAccessOverview();
const userAccess = accessOverView.find(
(overviewRow) => overviewRow.userId == user.id,
(overviewRow) => overviewRow.userId === 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 targetedEnvironment = environments.find(
(env) => env.name == enabledEnvName,
(env) => env.name === enabledEnvName,
);
const allOtherEnvironments = environments
.filter((x) => x.name != enabledEnvName)
.filter((x) => x.name !== enabledEnvName)
.map((env) => env.enabled);
expect(targetedEnvironment?.enabled).toBe(true);
@ -184,7 +184,7 @@ test('Passing an empty override does nothing', async () => {
const environments = await service.getAll();
const targetedEnvironment = environments.find(
(env) => env.name == enabledEnvName,
(env) => env.name === enabledEnvName,
);
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 targetedEnvironment = environments.find(
(env) => env.name == defaultEnvironment,
(env) => env.name === defaultEnvironment,
);
const allOtherEnvironments = environments
.filter((x) => x.name != defaultEnvironment)
.filter((x) => x.name !== defaultEnvironment)
.map((env) => env.enabled);
const envNames = environments.map((x) => x.name);

View File

@ -351,8 +351,8 @@ test('cloning a feature toggle copies variant environments correctly', async ()
newToggleName,
'default',
clonedToggleName,
true,
'test-user',
true,
);
const clonedToggle =
@ -379,8 +379,8 @@ test('cloning a feature toggle not allowed for change requests enabled', async (
'newToggleName',
'default',
'clonedToggleName',
true,
'test-user',
true,
),
).rejects.toEqual(
new ForbiddenError(
@ -407,7 +407,7 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
const featureName = 'ToggleWithSegments';
const clonedFeatureName = 'AWholeNewFeatureToggle';
let segment = await segmentService.create(
const segment = await segmentService.create(
{
name: 'SomeSegment',
constraints: mockConstraints(),
@ -442,11 +442,13 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
featureName,
'default',
clonedFeatureName,
true,
'test-user',
true,
);
let feature = await service.getFeature({ featureName: clonedFeatureName });
const feature = await service.getFeature({
featureName: clonedFeatureName,
});
expect(
feature.environments.find((x) => x.name === 'default')?.strategies[0]
.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');
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[0].id).toBe(projectUser.id);
@ -958,7 +958,7 @@ test('should change a users role in the project', async () => {
'test',
);
let { users: updatedUsers } = await projectService.getAccessToProject(
const { users: updatedUsers } = await projectService.getAccessToProject(
project.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,
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
@ -155,13 +155,13 @@ test('Should import variants in new format (per environment)', async () => {
keepExisting: false,
dropBeforeImport: true,
});
let exportedJson = await stateService.export({});
const exportedJson = await stateService.export({});
await stateService.import({
data: exportedJson,
keepExisting: false,
dropBeforeImport: true,
});
let featureEnvironments = await stores.featureEnvironmentStore.getAll();
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
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'),
).rejects.toThrow(
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'),
).rejects.toThrow(
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',
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 () => {
@ -330,5 +330,5 @@ test('should throw if autoCreate is false via SSO', async () => {
name: 'some',
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(
[0, 1, 2, 3, 4, 5].map(async (id) => {
const event =
id % 2 == 0
id % 2 === 0
? new FeatureCreatedEvent({
project: data.project,
featureName: data.name,

View File

@ -24,8 +24,8 @@ afterEach(async () => {
});
test('Setting enabled to same as existing value returns 0', async () => {
let envName = 'enabled-to-true';
let featureName = 'enabled-to-true-feature';
const envName = 'enabled-to-true';
const featureName = 'enabled-to-true-feature';
await environmentStore.create({
name: envName,
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 () => {
let envName = 'enabled-toggle';
let featureName = 'enabled-toggle-feature';
const envName = 'enabled-toggle';
const featureName = 'enabled-toggle-feature';
await environmentStore.create({
name: envName,
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 () => {
const name = 'feature.with.no.tags';
await featureToggleStore.create('default', { name });
let tags = await featureTagStore.getAllTagsForFeature(name);
const tags = await featureTagStore.getAllTagsForFeature(name);
expect(tags).toHaveLength(0);
});

View File

@ -11,7 +11,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
environments: IEnvironment[] = [];
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))
env.enabled = false;
}
@ -19,7 +19,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
}
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))
env.enabled = true;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ export default class FakeRoleStore implements IRoleStore {
}
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[]> {

1272
yarn.lock

File diff suppressed because it is too large Load Diff