chore: standardised the eslint config structure accross the project (#11814)

This commit is contained in:
Mridang Agarwalla
2026-03-12 11:02:52 +01:00
committed by GitHub
parent 621fd9bdec
commit 7cdee1703f
61 changed files with 1025 additions and 2011 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ export default async function Layout(props: { children: ReactNode; params: Promi
}
});
let tree = source.pageTree;
let tree: typeof source.pageTree;
if (currentVersion !== 'latest') {
// Hoist the version folder to root to flatten sidebar
// versionSource.pageTree -> [ v4.10 folder, v4.9 folder ... ]
+2 -2
View File
@@ -2,14 +2,14 @@
import { RootProvider } from 'fumadocs-ui/provider/next';
import dynamic from "next/dynamic";
import { ThemeProvider } from 'next-themes';
import { ReactNode } from 'react';
import MixpanelProvider from '@/components/mixpanel-provider';
import PlausibleProvider from '@/components/plausible-provider';
const SearchDialog = dynamic(() => import("@/components/inkeep-search"));
export function Providers({ children }: { children: React.ReactNode }) {
export function Providers({ children }: { children: ReactNode }) {
return (
<RootProvider
search={{
@@ -1,10 +1,6 @@
import React, { useMemo } from 'react';
import yaml from 'js-yaml';
import { Check, X } from 'lucide-react';
import { cn } from '@/utils/cn'; // Assuming utils/cn exists, or I'll use clsx/tailwind-merge directly if not sure
// Fallback utility if generic cn doesn't exist, checking imports first is safer, but I'll inline a simple merge if needed
// Checking package.json I saw clsx and tailwind-merge are dependencies.
import { Check } from 'lucide-react';
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import { initMixpanel, mixpanelClient, optInTracking, optOutTracking } from '@/utils/mixpanel';
import { initMixpanel, optInTracking, optOutTracking } from '@/utils/mixpanel';
import React, { useEffect } from 'react';
export default function MixpanelProvider({ children }: { children: React.ReactNode }) {
+90 -96
View File
@@ -1,101 +1,58 @@
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const mdx = require('eslint-plugin-mdx');
const mdxParser = require('eslint-mdx');
const tsParser = require('@typescript-eslint/parser');
const tsPlugin = require('@typescript-eslint/eslint-plugin');
import globals from 'globals';
import js from '@eslint/js';
// import { FlatCompat } from '@eslint/eslintrc';
// import path from 'path';
// import { fileURLToPath } from 'url';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
// const compat = new FlatCompat({
// baseDirectory: __dirname,
// });
import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier/flat";
import importPlugin from "eslint-plugin-import";
import * as mdx from "eslint-plugin-mdx";
import globals from "globals";
export default [
// 1. Base JS Rules
{
ignores: [
"node_modules/",
".next/",
"out/",
"build/",
".source/",
"next-env.d.ts",
],
},
js.configs.recommended,
// 2. Import Next.js Core Web Vitals (via compat because it doesn't support Flat Config natively yet)
// ...compat.extends('next/core-web-vitals'),
// ...compat.extends('next', 'next/typescript'), // Causes circular JSON error
// ...
// 3. MDX Configuration
{
files: ['*.mdx'],
...mdx.flat,
processor: mdx.createRemarkProcessor({
lintCodeBlocks: false,
}),
languageOptions: {
globals: {
...globals.browser,
...globals.node,
props: 'readonly',
},
parser: mdxParser,
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'react/no-unescaped-entities': 'off',
'no-unused-vars': 'off',
'no-irregular-whitespace': 'off',
'no-useless-escape': 'off',
},
},
// ...
// 4. Code Blocks inside MDX (virtual files)
{
files: ['*.mdx/*.{js,jsx,ts,tsx}'],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
props: 'readonly', // common in MDX
}
},
rules: {
'no-unused-vars': 'off',
'import/no-anonymous-default-export': 'off',
},
},
// 5. Global Ignores
{
ignores: ["node_modules/", ".next/", "out/", "build/", ".source/", "next-env.d.ts"],
},
// 6. Global options for JS files
{
files: ['**/*.{js,mjs,cjs}'],
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
}
},
},
// 7. Manual TypeScript Configuration
{
files: ['**/*.{ts,tsx}'],
files: ["**/*.jsx"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
rules: {
"no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^React$" }],
},
},
{
files: ["**/*.{ts,tsx}"],
plugins: {
'@typescript-eslint': tsPlugin,
"@typescript-eslint": tsPlugin,
import: importPlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: { jsx: true },
},
globals: {
@@ -103,24 +60,61 @@ export default [
...globals.node,
},
},
settings: {
"import/resolver": {
typescript: true,
},
},
rules: {
...tsPlugin.configs.recommended.rules,
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'off', // Disable for legacy code
'@typescript-eslint/ban-ts-comment': 'off',
...importPlugin.configs.recommended.rules,
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_" },
],
"no-undef": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
},
},
// 8. Scripts Config
{
files: ['scripts/**/*.{js,mjs,ts}'],
files: ["**/*.mdx"],
...mdx.flat,
processor: mdx.createRemarkProcessor({
lintCodeBlocks: false,
}),
languageOptions: {
...mdx.flat.languageOptions,
globals: {
...globals.browser,
...globals.node,
props: "readonly",
},
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-undef': 'off',
'no-useless-escape': 'off',
'no-empty': 'off',
'no-async-promise-executor': 'off',
}
}
"no-unused-vars": "off",
"no-irregular-whitespace": "off",
"no-useless-escape": "off",
},
},
{
files: ["**/*.mdx/*.{js,jsx,ts,tsx}"],
plugins: {
import: importPlugin,
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
props: "readonly",
},
},
rules: {
"no-unused-vars": "off",
},
},
prettierConfig,
];
+9 -8
View File
@@ -7,8 +7,8 @@
"generate:index-pages": "tsx scripts/generate-index-pages.ts",
"fetch:remote-content": "node scripts/fetch-remote-content.mjs",
"generate:proto-docs": "node scripts/generate-proto-docs.mjs",
"generate:api-reference": "tsx scripts/generate-api-reference.mts --only-generate",
"fix:api-links": "tsx scripts/generate-api-reference.mts --only-fix",
"generate:api-reference": "tsx scripts/generate-api-reference.ts --only-generate",
"fix:api-links": "tsx scripts/generate-api-reference.ts --only-fix",
"build:mdx-source": "fumadocs-mdx",
"build": "next build",
"dev": "next dev",
@@ -16,7 +16,7 @@
"start": "next start",
"clean": "node scripts/clean.mjs",
"check:types": "fumadocs-mdx && next typegen && tsc --noEmit",
"check:links": "tsx scripts/check-links.mts",
"check:links": "tsx scripts/check-links.ts",
"test:scripts": "node --test scripts/*.test.mjs",
"postinstall": "fumadocs-mdx",
"lint": "eslint ."
@@ -49,7 +49,6 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^10.0.1",
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/typography": "^0.5.19",
@@ -57,11 +56,13 @@
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.56.0",
"@typescript-eslint/parser": "^8.56.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"eslint": "^10.0.0",
"eslint-config-next": "16.1.6",
"eslint-plugin-mdx": "^3.6.2",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-mdx": "^3.7.0",
"glob": "^13.0.5",
"globals": "^17.3.0",
"next-validate-link": "^1.6.4",
@@ -10,7 +10,6 @@ register('fumadocs-mdx/node/loader', import.meta.url);
import {
type FileObject,
printErrors,
scanURLs,
validateFiles,
} from 'next-validate-link';
@@ -1,6 +1,5 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import yaml from 'js-yaml';
const V1_BASE_PATHS: Record<string, string> = {
'admin.openapi.json': '/admin/v1',
+1 -1
View File
@@ -32,7 +32,7 @@ async function run() {
}
// Versioned content and protos
const cleanVersioned = (dir, pattern) => {
const cleanVersioned = (dir) => {
if (!fs.existsSync(dir)) return;
const files = fs.readdirSync(dir);
for (const file of files) {
+6 -6
View File
@@ -71,7 +71,7 @@ function getCurrentRef() {
cachedRef = branch;
return cachedRef;
}
} catch (e) {
} catch {
// Ignore git errors
}
console.log(`[ref] Defaulting to ${FALLBACK_BRANCH}`);
@@ -143,7 +143,7 @@ function copyDirectorySafely(src, dest) {
if (versionDirPattern.test(item) && fs.statSync(join(src, item)).isDirectory()) {
isVersionDir = true;
}
} catch (e) {
} catch {
// Ignore stat errors
}
@@ -281,7 +281,7 @@ async function downloadFileContent(tagOrBranch, repoPath) {
let decodedRepoPath;
try {
decodedRepoPath = decodeURIComponent(repoPath.replace(/\\/g, '/'));
} catch (e) {
} catch {
// If decoding fails (malformed escape sequences), fall back to original
decodedRepoPath = repoPath;
}
@@ -370,7 +370,7 @@ async function fixRelativeImports(versionDir, tagOrBranch) {
});
// 2. Markdown Images: ![alt](src)
const mdImgRegex = /(!\[.*?\]\()([^\)]+)(\))/g;
const mdImgRegex = /(!\[.*?\]\()([^)]+)(\))/g;
content = content.replace(mdImgRegex, (match, p1, p2, p3) => {
if (!p2.startsWith('.')) return match;
const rewritten = rewritePath(filePath, p2);
@@ -452,7 +452,7 @@ function getLocalVersion() {
if (!branch) {
try {
branch = execSync('git branch --show-current').toString().trim();
} catch (e) {}
} catch { /* ignore git errors */ }
}
if (branch && branch !== 'main' && branch !== 'master') {
@@ -467,7 +467,7 @@ function getLocalVersion() {
if (semver.valid(tag) && semver.gt(tag, CUTOFF)) {
return { label: tag, isUnreleased: false };
}
} catch (e) { }
} catch { /* ignore git errors */ }
return { label: 'v4.11.0', isUnreleased: true };
}
@@ -4,7 +4,6 @@ import { writeFileSync, mkdirSync, readdirSync, lstatSync, readFileSync, existsS
import path, { join, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
import yaml from 'js-yaml';
// Suppress "Generated: ..." logs to avoid Vercel log limits
const originalLog = console.log;
@@ -147,7 +146,7 @@ async function fixAllGeneratedLinks() {
const versionMap = fileIndex.get(version);
// [Text](apis/resources/service_name/file_name.api.mdx)
const linkRegex = /\[([^\]]+)\]\(([\/]?apis\/resources\/([^\/]+)\/([^\/)]+)\.api\.mdx)\)/g;
const linkRegex = /\[([^\]]+)\]\(([/]?apis\/resources\/([^/]+)\/([^/)]+)\.api\.mdx)\)/g;
let modified = false;
let newContent = content.replace(linkRegex, (match, text, fullLink, serviceSlug, fileSlug) => {
@@ -178,7 +177,7 @@ async function fixAllGeneratedLinks() {
// Fix v2beta links that were likely in the source proto comments
// and also remove potential double /docs prefixing from source comments
const internalLinkRegex = /\[([^\]]+)\]\(([\/]?docs\/)?reference\/api\/([^\/]+)\/([^\s)]+)\)/g;
const internalLinkRegex = /\[([^\]]+)\]\(([/]?docs\/)?reference\/api\/([^/]+)\/([^\s)]+)\)/g;
newContent = newContent.replace(internalLinkRegex, (match, text, docsPrefix, service, fileSlug) => {
let targetFileSlug = fileSlug;
let isV2Beta = fileSlug.includes('.v2beta.');
@@ -87,6 +87,7 @@ async function run() {
// Use spawn (async) instead of spawnSync to avoid blocking the event loop
// eslint-disable-next-line no-async-promise-executor
await new Promise(async (resolvePromise, rejectPromise) => {
// Dynamic discovery of excluded paths
const getExcludedPaths = async () => {
-4
View File
@@ -43,10 +43,6 @@ walk(CONTENT_DIR, (filepath) => {
// Replaces /docs/img/ -> correctPrefix + img/
// Replaces /docs/vX.Y/ -> correctPrefix + vX.Y/
const PUBLIC_PREFIX = '/docs/img/';
const VERSION_PREFIXES = ['/docs/v4.10/', '/docs/v4.9/', '/docs/v4.8/'];
const TARGET_PATHS = [PUBLIC_PREFIX, ...VERSION_PREFIXES];
// Pattern: (!\[.*?\]\(|src=["'])(/docs/(?:img|v\d+\.\d+)/.*?)(\)|["'])
const pattern = /(!\[.*?\]\(|src=["'])\/docs\/((?:img|v\d+\.\d+)\/.*?)(\)|["'])/g;
-2
View File
@@ -1,5 +1,4 @@
import fs from 'fs';
import path from 'path';
const REDIRECTS_PATH = '/home/ffo/git/zitadel/zitadel/apps/docs/redirects.json';
@@ -37,7 +36,6 @@ function validateRedirects() {
});
// 2. Circular & Chains
const checked = new Set();
sources.forEach((value, source) => {
let currentSource = source;
const path = [currentSource];
-79
View File
@@ -1,79 +0,0 @@
module.exports = {
parser: "@typescript-eslint/parser",
extends: ["next", "prettier"],
plugins: ["@typescript-eslint", "import"],
rules: {
"import/first": "error",
"@next/next/no-html-link-for-pages": "off",
"@next/next/no-img-element": "off",
"react/no-unescaped-entities": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", {
argsIgnorePattern: "^_" ,
varsIgnorePattern: "^_" ,
}],
"no-undef": "off",
"no-restricted-imports": ["error", {
"paths": [{
"name": "next/image",
"message": "Use of next/image is forbidden. Use regular <img> elements instead."
}]
}],
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
project: "./tsconfig.json",
},
overrides: [
{
// Server components and server actions: must use logger, not console.*
// Files in src/lib/server/*, src/app/**/route.ts, src/middleware.ts, and files with "use server"
files: [
"src/lib/server/**/*.ts",
"src/app/**/route.ts",
"src/middleware.ts",
"src/lib/cookies.ts",
],
rules: {
"no-console": "warn",
},
},
{
// instrumentation.ts uses console.log during OTEL initialization (before logger is available)
files: ["src/instrumentation.ts"],
rules: {
"no-console": "off",
},
},
{
// Client components: logger import is forbidden (enforced by server-only at build time)
// This override documents the expectation for client components
files: ["src/components/**/*.tsx"],
rules: {
"no-restricted-imports": ["error", {
"paths": [
{
"name": "next/image",
"message": "Use of next/image is forbidden. Use regular <img> elements instead."
},
{
"name": "@/lib/logger",
"message": "Cannot import logger in client components. Use console.* instead. The logger uses 'server-only' and will fail at build time."
}
]
}],
},
},
{
// Test files: allow console.*
files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/tests/**/*"],
rules: {
"no-console": "off",
},
},
],
};
-10
View File
@@ -1,10 +0,0 @@
module.exports = {
root: true,
// Use basic ESLint config since the login app has its own detailed config
extends: ["eslint:recommended"],
settings: {
next: {
rootDir: ["apps/*/"],
},
},
};
+30
View File
@@ -0,0 +1,30 @@
import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier/flat";
export default [
{ ignores: ["node_modules/**"] },
js.configs.recommended,
{
files: ["**/*.ts"],
plugins: {
"@typescript-eslint": tsPlugin,
},
languageOptions: {
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
...tsPlugin.configs.recommended.rules,
"no-unused-vars": "off",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
},
},
prettierConfig,
];
+149
View File
@@ -0,0 +1,149 @@
import js from "@eslint/js";
import nextPlugin from "@next/eslint-plugin-next";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier/flat";
import importPlugin from "eslint-plugin-import";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import globals from "globals";
export default [
{
ignores: [
"node_modules/**",
".next/**",
"dockerized/**",
"cypress/**",
"acceptance/**",
"integration/**",
"vitest.config*.ts",
"next-env.d.ts",
],
},
js.configs.recommended,
nextPlugin.configs["core-web-vitals"],
{
files: ["**/*.{ts,tsx,jsx}"],
plugins: {
"react-hooks": reactHooksPlugin,
},
rules: {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
},
},
{
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ["**/*.{ts,tsx}"],
plugins: {
"@typescript-eslint": tsPlugin,
import: importPlugin,
},
languageOptions: {
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
parserOptions: {
ecmaFeatures: { jsx: true },
project: "./tsconfig.json",
},
},
settings: {
"import/resolver": {
typescript: true,
},
},
rules: {
...tsPlugin.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"no-undef": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@next/next/no-img-element": "off",
"no-restricted-imports": [
"error",
{
paths: [
{
name: "next/image",
message:
"Use of next/image is forbidden. Use regular <img> elements instead.",
},
],
},
],
},
},
// Server components and server actions: must use logger, not console.*
{
files: [
"src/lib/server/**/*.ts",
"src/app/**/route.ts",
"src/middleware.ts",
"src/lib/cookies.ts",
],
rules: {
"no-console": "warn",
},
},
// instrumentation.ts uses console.log during OTEL initialization (before logger is available)
{
files: ["src/instrumentation.ts"],
rules: {
"no-console": "off",
},
},
// Client components: logger import is forbidden (enforced by server-only at build time)
{
files: ["src/components/**/*.tsx"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "next/image",
message:
"Use of next/image is forbidden. Use regular <img> elements instead.",
},
{
name: "@/lib/logger",
message:
"Cannot import logger in client components. Use console.* instead. The logger uses 'server-only' and will fail at build time.",
},
],
},
],
},
},
// Test files: allow console.*
{
files: [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/tests/**/*",
],
rules: {
"no-console": "off",
},
},
prettierConfig,
];
-10
View File
@@ -1,10 +0,0 @@
module.exports = {
root: true,
// Use basic ESLint config since the login app has its own detailed config
extends: ["eslint:recommended"],
settings: {
next: {
rootDir: ["apps/*/"],
},
},
};
+32
View File
@@ -0,0 +1,32 @@
import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier/flat";
import pluginCypress from "eslint-plugin-cypress";
export default [
{ ignores: ["node_modules/**"] },
js.configs.recommended,
{
files: ["**/*.ts"],
plugins: {
"@typescript-eslint": tsPlugin,
},
languageOptions: {
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
...tsPlugin.configs.recommended.rules,
"no-unused-vars": "off",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
},
},
pluginCypress.configs.recommended,
prettierConfig,
];
+12 -8
View File
@@ -8,11 +8,11 @@
"build-vercel": "next build",
"dev": "HOSTNAME=127.0.0.1 ./scripts/entrypoint.sh next dev",
"prod": "cd ./.next/standalone && HOSTNAME=127.0.0.1 ./entrypoint.sh node apps/login/server.js",
"lint-check-next": "next lint",
"lint-check-next": "eslint .",
"lint-check-prettier": "prettier --check .",
"lint-fix": "prettier --write .",
"test-unit": "vitest --run",
"test-dockerized": "vitest --run --config vitest.config.dockerized.mts",
"test-dockerized": "vitest --run --config vitest.config.dockerized.ts",
"lint-staged": "lint-staged",
"clean": "rm -rf node_modules .next cypress"
},
@@ -55,8 +55,9 @@
"winston": "^3.17.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.28.6",
"@eslint/js": "^10.0.0",
"@faker-js/faker": "^9.9.0",
"@next/eslint-plugin-next": "16.1.6",
"@opentelemetry/sdk-trace-base": "^2.5.0",
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@otplib/core": "^12.0.1",
@@ -72,8 +73,8 @@
"@types/react-dom": "19.2.3",
"@types/tinycolor2": "^1.4.6",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"@vercel/git-hooks": "1.0.0",
"@vitejs/plugin-react": "^4.7.0",
"@zitadel/client": "workspace:*",
@@ -84,11 +85,14 @@
"dotenv-cli": "^8.0.0",
"env-cmd": "^10.1.0",
"esbuild": "^0.27.3",
"eslint": "^8.57.1",
"eslint-config-next": "15.5.9",
"eslint-config-prettier": "^9.1.2",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-cypress": "^4.2.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react-hooks": "^7.0.1",
"gaxios": "^7.1.3",
"globals": "^17.0.0",
"grpc-tools": "^1.13.1",
"jsdom": "^26.1.0",
"lint-staged": "15.5.1",
+2 -2
View File
@@ -129,7 +129,7 @@
"options": {
"cwd": "{projectRoot}",
"commands": [
"pnpm vitest --run --config vitest.config.dockerized.mts"
"pnpm vitest --run --config vitest.config.dockerized.ts"
]
}
},
@@ -149,7 +149,7 @@
"options": {
"cwd": "{projectRoot}",
"commands": [
"pnpm vitest --run --config vitest.config.dockerized.mts dockerized/ssl"
"pnpm vitest --run --config vitest.config.dockerized.ts dockerized/ssl"
]
}
},
+11 -20
View File
@@ -13,30 +13,21 @@ interface AvatarProps {
}
export function getInitials(name: string, loginName: string) {
let credentials = "";
if (name) {
const split = name.split(" ");
if (split) {
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : "");
credentials = initials;
} else {
credentials = name.charAt(0);
}
} else {
const username = loginName.split("@")[0];
let separator = "_";
if (username.includes("-")) {
separator = "-";
}
if (username.includes(".")) {
separator = ".";
}
const split = username.split(separator);
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : "");
credentials = initials;
return split[0].charAt(0) + (split[1] ? split[1].charAt(0) : "");
}
return credentials;
const username = loginName.split("@")[0];
let separator = "_";
if (username.includes("-")) {
separator = "-";
}
if (username.includes(".")) {
separator = ".";
}
const split = username.split(separator);
return split[0].charAt(0) + (split[1] ? split[1].charAt(0) : "");
}
// Helper function to get avatar roundness from theme
-1
View File
@@ -65,7 +65,6 @@ function getDefaultButtonAppearance(): string {
return appearance?.button || "border border-button-light-border dark:border-button-dark-border"; // Fallback to flat design
}
// eslint-disable-next-line react/display-name
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
-1
View File
@@ -35,7 +35,6 @@ function getDefaultCardStyling(): string {
return appearance?.card || "shadow-sm border-0"; // Fallback to material design
}
// eslint-disable-next-line react/display-name
export const Card = forwardRef<HTMLDivElement, CardProps>(
(
{
+1 -1
View File
@@ -26,7 +26,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Che
checked={enabled}
onChange={(event) => {
setEnabled(event.target?.checked);
onChangeVal && onChangeVal(event.target?.checked);
if (onChangeVal) onChangeVal(event.target?.checked);
}}
disabled={disabled}
type="checkbox"
@@ -17,7 +17,7 @@ export function CopyToClipboard({ value }: Props) {
const to = setTimeout(setCopied, 1000, false);
return () => clearTimeout(to);
}
}, [copied]);
}, [copied, value]);
return (
<div className="flex flex-row items-center px-2">
@@ -91,7 +91,7 @@ export function IdpProcessHandler({
setError(err instanceof Error ? err.message : t("processing.unexpectedError"));
setLoading(false);
});
}, [provider, id, token, requestId, organization, link, sessionId, postErrorRedirectUrl, router]);
}, [provider, id, token, requestId, organization, link, sessionId, linkFingerprint, postErrorRedirectUrl, router, t]);
return (
<div className="flex min-h-screen items-center justify-center">
-1
View File
@@ -37,7 +37,6 @@ function getDefaultInputRoundness(): string {
return getComponentRoundness("input");
}
// eslint-disable-next-line react/display-name
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
(
{
+31 -28
View File
@@ -11,7 +11,7 @@ import { Button, ButtonVariants } from "./button";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
type Inputs = {};
type Inputs = Record<string, never>;
type Props = {
sessionId?: string;
@@ -44,6 +44,35 @@ export function RegisterPasskey({
const router = useRouter();
const continueAndLogin = useCallback(
(loginName?: string) => {
const params = new URLSearchParams();
if (organization) {
params.set("organization", organization);
}
if (requestId) {
params.set("requestId", requestId);
}
if (sessionId) {
params.set("sessionId", sessionId);
}
if (userId) {
params.set("userId", userId);
}
if (loginName ?? initialLoginName) {
params.set("loginName", loginName ?? initialLoginName ?? "");
}
router.push("/passkey?" + params);
},
[organization, requestId, sessionId, userId, initialLoginName, router],
);
async function submitVerify(
passkeyId: string,
passkeyName: string,
@@ -166,7 +195,7 @@ export function RegisterPasskey({
}
continueAndLogin(verificationResponse.loginName);
}, [sessionId, userId, code]);
}, [sessionId, userId, code, codeId, continueAndLogin]);
// Auto-submit when code is provided (similar to VerifyForm)
useEffect(() => {
@@ -175,32 +204,6 @@ export function RegisterPasskey({
}
}, [code, submitRegisterAndContinue]);
function continueAndLogin(loginName?: string) {
const params = new URLSearchParams();
if (organization) {
params.set("organization", organization);
}
if (requestId) {
params.set("requestId", requestId);
}
if (sessionId) {
params.set("sessionId", sessionId);
}
if (userId) {
params.set("userId", userId);
}
if (loginName ?? initialLoginName) {
params.set("loginName", loginName ?? initialLoginName ?? "");
}
router.push("/passkey?" + params);
}
return (
<form className="w-full">
{error && (
@@ -4,7 +4,7 @@ import { clearSession } from "@/lib/server/session";
import { timestampDate } from "@zitadel/client";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { redirect, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { Alert, AlertType } from "./alert";
import { SessionClearItem } from "./session-clear-item";
import { Translated } from "./translated";
@@ -20,7 +20,7 @@ export function SessionsClearList({ sessions, logoutHint, postLogoutRedirectUri,
const [list, setList] = useState<Session[]>(sessions);
const router = useRouter();
async function clearHintedSession() {
const clearHintedSession = useCallback(async () => {
console.log("Clearing session for login hint:", logoutHint);
// If a login hint is provided, we logout that specific session
const sessionIdToBeCleared = sessions.find((session) => {
@@ -53,13 +53,13 @@ export function SessionsClearList({ sessions, logoutHint, postLogoutRedirectUri,
} else {
console.warn(`No session found for login hint: ${logoutHint}`);
}
}
}, [logoutHint, sessions, postLogoutRedirectUri, organization, router]);
useEffect(() => {
if (logoutHint) {
clearHintedSession();
}
}, []);
}, [logoutHint, clearHintedSession]);
return sessions ? (
<div className="flex flex-col space-y-2">
+24 -21
View File
@@ -5,7 +5,7 @@ import { sendLoginname } from "@/lib/server/loginname";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Alert } from "./alert";
import { AutoSubmitForm } from "./auto-submit-form";
@@ -55,34 +55,37 @@ export function UsernameForm({
const [error, setError] = useState<string>("");
const [samlData, setSamlData] = useState<{ url: string; fields: Record<string, string> } | null>(null);
async function submitLoginName(values: Inputs, organization?: string) {
setLoading(true);
const submitLoginName = useCallback(
async (values: Inputs, organization?: string) => {
setLoading(true);
try {
const res = await sendLoginname({
loginName: values.loginName,
organization,
defaultOrganization,
requestId,
suffix,
ignoreUnknownUsernames: loginSettings?.ignoreUnknownUsernames,
});
try {
const res = await sendLoginname({
loginName: values.loginName,
organization,
defaultOrganization,
requestId,
suffix,
ignoreUnknownUsernames: loginSettings?.ignoreUnknownUsernames,
});
handleServerActionResponse(res, router, setSamlData, setError);
return res;
} catch {
setError(t("errors.internalError"));
} finally {
setLoading(false);
}
}
handleServerActionResponse(res, router, setSamlData, setError);
return res;
} catch {
setError(t("errors.internalError"));
} finally {
setLoading(false);
}
},
[defaultOrganization, requestId, suffix, loginSettings, router, t],
);
useEffect(() => {
if (submit && loginName) {
// When we navigate to this page, we always want to be redirected if submit is true and the parameters are valid.
submitLoginName({ loginName }, organization);
}
}, []);
}, [submit, loginName, organization, submitLoginName]);
let inputLabel = t("labels.loginname");
if (loginSettings?.disableLoginWithEmail && loginSettings?.disableLoginWithPhone) {
+1 -1
View File
@@ -101,7 +101,7 @@ export function VerifyForm({ userId, loginName, organization, requestId, code, i
setLoading(false);
}
},
[isInvite, userId],
[isInvite, userId, loginName, organization, requestId, router, t],
);
useEffect(() => {
+15 -34
View File
@@ -341,58 +341,39 @@ export function hashCode(str: string, seed = 0): number {
export function getMembershipColor(role: string): ColorShade {
const hash = hashCode(role);
let color = COLORS[hash % COLORS.length];
switch (role) {
case "IAM_OWNER":
color = COLORS[0];
break;
return COLORS[0];
case "IAM_OWNER_VIEWER":
color = COLORS[14];
break;
return COLORS[14];
case "IAM_ORG_MANAGER":
color = COLORS[11];
break;
return COLORS[11];
case "IAM_USER_MANAGER":
color = COLORS[8];
break;
return COLORS[8];
case "ORG_OWNER":
color = COLORS[16];
break;
return COLORS[16];
case "ORG_USER_MANAGER":
color = COLORS[8];
break;
return COLORS[8];
case "ORG_OWNER_VIEWER":
color = COLORS[14];
break;
return COLORS[14];
case "ORG_USER_PERMISSION_EDITOR":
color = COLORS[7];
break;
return COLORS[7];
case "ORG_PROJECT_PERMISSION_EDITOR":
color = COLORS[11];
break;
return COLORS[11];
case "ORG_PROJECT_CREATOR":
color = COLORS[12];
break;
return COLORS[12];
case "PROJECT_OWNER":
color = COLORS[9];
break;
return COLORS[9];
case "PROJECT_OWNER_VIEWER":
color = COLORS[10];
break;
return COLORS[10];
case "PROJECT_OWNER_GLOBAL":
color = COLORS[11];
break;
return COLORS[11];
case "PROJECT_OWNER_VIEWER_GLOBAL":
color = COLORS[12];
break;
return COLORS[12];
default:
color = COLORS[hash % COLORS.length];
break;
return COLORS[hash % COLORS.length];
}
return color;
}
+1 -1
View File
@@ -52,7 +52,7 @@ export default getRequestConfig(async () => {
const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware
let translations: JsonObject | {} = {};
let translations: JsonObject | Record<string, never> = {};
try {
const i18nJSON = await getHostedLoginTranslation({
serviceConfig,
+3 -1
View File
@@ -46,6 +46,8 @@ export async function loginServiceKeyToken() {
key: key,
});
} catch (err) {
throw new Error(`Failed to read login service key file "${keyFile}": ${err instanceof Error ? err.message : err}`);
throw new Error(`Failed to read login service key file "${keyFile}": ${err instanceof Error ? err.message : err}`, {
cause: err,
});
}
}
+34 -29
View File
@@ -374,24 +374,26 @@ export async function sendLoginname(command: SendLoginnameCommand) {
};
}
const paramsPassword = new URLSearchParams({
loginName: command.ignoreUnknownUsernames
? command.loginName
: (session?.factors?.user?.loginName ?? user.preferredLoginName),
});
{
const paramsPassword = new URLSearchParams({
loginName: command.ignoreUnknownUsernames
? command.loginName
: (session?.factors?.user?.loginName ?? user.preferredLoginName),
});
if (organization) {
paramsPassword.append("organization", organization);
if (organization) {
paramsPassword.append("organization", organization);
}
if (command.requestId) {
paramsPassword.append("requestId", command.requestId);
}
return {
redirect: "/password?" + paramsPassword,
};
}
if (command.requestId) {
paramsPassword.append("requestId", command.requestId);
}
return {
redirect: "/password?" + paramsPassword,
};
case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
if (userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED || !userLoginSettings?.allowLocalAuthentication) {
if (command.ignoreUnknownUsernames) {
@@ -402,22 +404,24 @@ export async function sendLoginname(command: SendLoginnameCommand) {
};
}
const paramsPasskey = new URLSearchParams({
loginName: command.ignoreUnknownUsernames
? command.loginName
: (session?.factors?.user?.loginName ?? user.preferredLoginName),
});
if (command.requestId) {
paramsPasskey.append("requestId", command.requestId);
{
const paramsPasskey = new URLSearchParams({
loginName: command.ignoreUnknownUsernames
? command.loginName
: (session?.factors?.user?.loginName ?? user.preferredLoginName),
});
if (command.requestId) {
paramsPasskey.append("requestId", command.requestId);
}
if (organization) {
paramsPasskey.append("organization", organization);
}
return { redirect: "/passkey?" + paramsPasskey };
}
if (organization) {
paramsPasskey.append("organization", organization);
}
return { redirect: "/passkey?" + paramsPasskey };
case AuthenticationMethodType.IDP:
case AuthenticationMethodType.IDP: {
const resp = await redirectUserToIDP(userId, organization);
if (resp?.error) {
@@ -425,6 +429,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
return resp;
}
}
} else {
// prefer passkey in favor of other methods
@@ -317,7 +317,6 @@ describe("sendPassword", () => {
const { completeFlowOrGetUrl } = await import("../client");
vi.mocked(completeFlowOrGetUrl).mockResolvedValue({ redirect: "https://example.com" });
// eslint-disable-next-line
const verifyHelper = await import("../verify-helper");
vi.mocked(verifyHelper.checkPasswordChangeRequired).mockReturnValue(undefined);
vi.mocked(verifyHelper.checkEmailVerification).mockReturnValue(undefined);
+2 -2
View File
@@ -293,7 +293,7 @@ export async function sendPassword(
return {
error: t(messageKey, {
failedAttempts: error.failedAttempts,
maxPasswordAttempts: hasLimit ? (lockoutSettings?.maxPasswordAttempts).toString() : "?",
maxPasswordAttempts: hasLimit ? String(lockoutSettings?.maxPasswordAttempts ?? 0) : "?",
lockoutMessage: locked ? t("errors.accountLockedContactAdmin") : "",
}),
};
@@ -565,7 +565,7 @@ export async function checkSessionAndSetPassword({
return {
error: t(messageKey, {
failedAttempts: error.failedAttempts,
maxPasswordAttempts: hasLimit ? (lockoutSettings?.maxPasswordAttempts).toString() : "?",
maxPasswordAttempts: hasLimit ? String(lockoutSettings?.maxPasswordAttempts ?? 0) : "?",
lockoutMessage: locked ? t("errors.accountLockedContactAdmin") : "",
}),
};
+2
View File
@@ -164,6 +164,7 @@ export async function checkMFAFactors(
// TODO: provide a way to setup passkeys on mfa page?
return { redirect: `/mfa/set?` + params };
/* eslint-disable no-dupe-else-if -- TODO: this branch is unreachable, conditions overlap with the previous branch */
} else if (
loginSettings?.mfaInitSkipLifetime &&
(loginSettings.mfaInitSkipLifetime.nanos > 0 || loginSettings.mfaInitSkipLifetime.seconds > 0) &&
@@ -171,6 +172,7 @@ export async function checkMFAFactors(
session?.factors?.user?.id &&
shouldEnforceMFA(session, loginSettings)
) {
/* eslint-enable no-dupe-else-if */
const userResponse = await getUserByID({ serviceConfig, userId: session.factors?.user?.id });
const humanUser = userResponse?.user?.type.case === "human" ? userResponse?.user.type.value : undefined;
+1 -3
View File
@@ -1237,20 +1237,18 @@ export interface ServiceConfig {
/**
* Base type that all function parameters must extend to ensure serviceConfig is always required
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export type WithServiceConfig<T = {}> = T & {
serviceConfig: ServiceConfig;
};
export function createServerTransport(token: string, serviceConfig: ServiceConfig) {
// Build interceptors list - OTEL interceptor is always included for trace propagation
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const interceptors: any[] = [otelGrpcInterceptor];
// Add custom headers interceptor if needed
if (process.env.CUSTOM_REQUEST_HEADERS || serviceConfig.instanceHost || serviceConfig.publicHost) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interceptors.push((next: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (req: any) => {
// Apply headers from serviceConfig
if (serviceConfig.instanceHost) {
+2 -1
View File
@@ -39,6 +39,7 @@
"node_modules",
"integration",
"acceptance",
"dockerized"
"dockerized",
"vitest.config*.ts"
]
}
-40
View File
@@ -1,40 +0,0 @@
module.exports = {
root: true,
ignorePatterns: ['projects/**/*'],
overrides: [
{
files: ['*.ts'],
parserOptions: {
project: ['tsconfig.json', 'e2e/tsconfig.json'],
createDefaultProgram: true,
tsconfigRootDir: __dirname,
},
extends: ['plugin:@angular-eslint/recommended', 'plugin:@angular-eslint/template/process-inline-templates'],
rules: {
'@angular-eslint/no-conflicting-lifecycle': 'off',
'@angular-eslint/no-host-metadata-property': 'off',
'@angular-eslint/component-selector': [
'error',
{
prefix: 'cnsl',
style: 'kebab-case',
type: 'element',
},
],
'@angular-eslint/directive-selector': [
'error',
{
prefix: 'cnsl',
style: 'camelCase',
type: 'attribute',
},
],
},
},
{
files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'],
rules: {},
},
],
};
+110
View File
@@ -0,0 +1,110 @@
import js from "@eslint/js";
import angularPlugin from "@angular-eslint/eslint-plugin";
import angularTemplatePlugin from "@angular-eslint/eslint-plugin-template";
import angularTemplateParser from "@angular-eslint/template-parser";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import importPlugin from "eslint-plugin-import";
import prettierConfig from "eslint-config-prettier/flat";
import globals from "globals";
export default [
{
ignores: [
"projects/**/*",
"dist/**",
"node_modules/**",
".angular/**",
"src/app/proto/generated/**",
],
},
js.configs.recommended,
{
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ["**/*.ts"],
plugins: {
"@typescript-eslint": tsPlugin,
"@angular-eslint": angularPlugin,
import: importPlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
project: ["tsconfig.json", "e2e/tsconfig.json"],
createDefaultProgram: true,
},
},
processor: angularTemplatePlugin.processors["extract-inline-html"],
settings: {
"import/resolver": {
typescript: true,
},
},
rules: {
...tsPlugin.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
// Proto imports are generated at build time and don't exist during lint
"import/no-unresolved": "off",
"no-unused-vars": "off",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_" },
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"@typescript-eslint/no-wrapper-object-types": "off",
"@typescript-eslint/no-empty-object-type": "off",
"no-case-declarations": "off",
...angularPlugin.configs.recommended.rules,
// Disable rules added in angular-eslint 21.x that weren't in the original 18.x config
"@angular-eslint/prefer-inject": "off",
"@angular-eslint/prefer-standalone": "off",
"@angular-eslint/no-conflicting-lifecycle": "off",
"@angular-eslint/no-host-metadata-property": "off",
"@angular-eslint/component-selector": [
"error",
{
prefix: "cnsl",
style: "kebab-case",
type: "element",
},
],
"@angular-eslint/directive-selector": [
"error",
{
prefix: "cnsl",
style: "camelCase",
type: "attribute",
},
],
},
},
{
files: ["**/*.html"],
plugins: {
"@angular-eslint/template": angularTemplatePlugin,
},
languageOptions: {
parser: angularTemplateParser,
},
rules: {
...angularTemplatePlugin.configs.recommended.rules,
// Disable rules added in angular-eslint 21.x that weren't in the original 18.x config
"@angular-eslint/template/prefer-control-flow": "off",
"@angular-eslint/template/eqeqeq": "off",
},
},
prettierConfig,
];
+12 -9
View File
@@ -5,7 +5,7 @@
"ng": "ng",
"dev": "wait-on --verbose --interval 2000 --simultaneous 1 --timeout 30m ${ENVIRONMENT_JSON_URL} && node prebuild.development.js && ng serve --host=${DEVCONTAINER_LISTEN_HOST:-127.0.0.1}",
"build": "ng build --configuration production --base-href=/ui/console/",
"lint-check-ng": "ng lint",
"lint-check-ng": "eslint .",
"lint-check-prettier": "prettier --check src",
"lint-fix": "prettier --write src",
"generate": "pnpm exec buf generate ../proto --include-imports --include-wkt",
@@ -59,15 +59,14 @@
"zone.js": "~0.15.1"
},
"devDependencies": {
"@angular-eslint/builder": "18.3.0",
"@angular-eslint/eslint-plugin": "18.0.0",
"@angular-eslint/eslint-plugin-template": "18.0.0",
"@angular-eslint/schematics": "19.8.1",
"@angular-eslint/template-parser": "19.8.1",
"@angular-eslint/eslint-plugin": "21.3.0",
"@angular-eslint/eslint-plugin-template": "21.3.0",
"@angular-eslint/template-parser": "21.3.0",
"@angular/build": "^20.3.15",
"@angular/cli": "^20.3.15",
"@angular/compiler-cli": "^20.3.16",
"@angular/language-service": "^20.3.16",
"@eslint/js": "^10.0.0",
"@types/file-saver": "^2.0.7",
"@types/google-protobuf": "~3.15.12",
"@types/jasmine": "~5.1.15",
@@ -77,11 +76,15 @@
"@types/opentype.js": "^1.3.9",
"@types/qrcode": "^1.5.6",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"@zitadel/client": "workspace:*",
"@zitadel/proto": "workspace:*",
"eslint": "^8.57.1",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"globals": "^17.0.0",
"jasmine-core": "~5.12.1",
"jasmine-spec-reporter": "~7.0.0",
"karma": "^6.4.4",
@@ -25,6 +25,8 @@ export class ScrollableDirective {
if (top === 0) {
this.scrollPosition.emit('top');
}
} catch (err) {}
} catch {
// scroll position detection may fail in some browsers
}
}
}
@@ -19,8 +19,6 @@ export class DisplayJsonDialogComponent {
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.event = data.event;
if ((data.event as Event) && data.event.payload) {
}
}
public closeDialog(): void {
@@ -235,6 +235,7 @@ export class IdpTableComponent implements OnInit, OnDestroy {
} else if (this.service instanceof ManagementService) {
return ['/org', 'idp', 'create'];
}
return undefined;
}
public routerLinkForRow(row: Provider.AsObject): any {
@@ -248,6 +248,7 @@ export class InputDirective implements MatFormFieldControl<any>, OnChanges, OnDe
this._previousNativeValue = this.value;
// Force setter to be called in case id was not specified.
// eslint-disable-next-line no-self-assign
this.id = this.id;
// On some versions of iOS the caret gets stuck in the wrong place when holding down the delete
@@ -48,7 +48,7 @@ export class DialogAddSMSProviderComponent {
});
this.smsProviders = data.smsProviders;
if (!!this.twilio) {
if (this.twilio) {
this.twilioForm.patchValue(this.twilio);
} else {
this.twilioForm.addControl('token', new FormControl('', requiredValidator));
@@ -61,7 +61,7 @@ export class NotificationSMSProviderComponent implements OnInit {
dialogRef.afterClosed().subscribe((req: AddSMSProviderTwilioRequest | UpdateSMSProviderTwilioRequest) => {
if (req) {
if (!!this.twilio) {
if (this.twilio) {
this.service
.updateSMSProviderTwilio(req as UpdateSMSProviderTwilioRequest)
.then(() => {
@@ -72,10 +72,9 @@ export class StatehandlerServiceImpl implements StatehandlerService, OnDestroy {
removeParam(key: string, sourceURL: string) {
var rtn = sourceURL.split('?')[0],
param,
params_arr = [],
queryString = sourceURL.indexOf('?') !== -1 ? sourceURL.split('?')[1] : '';
if (queryString !== '') {
params_arr = queryString.split('&');
var params_arr = queryString.split('&');
for (var i = params_arr.length - 1; i >= 0; i -= 1) {
param = params_arr[i].split('=')[0];
if (param === key) {
+1 -1
View File
@@ -72,7 +72,7 @@ export function getColor(toGen: string): string {
export function getMembershipColor(role: string): Color {
const hash = hashCode(role);
let color = COLORS[hash % COLORS.length];
let color: Color;
switch (role) {
case 'IAM_OWNER':
-17
View File
@@ -1,17 +0,0 @@
module.exports = {
root: true,
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
env: {
node: true,
es2022: true,
},
parserOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }]
}
};
+44
View File
@@ -0,0 +1,44 @@
import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier/flat";
import importPlugin from "eslint-plugin-import";
import globals from "globals";
export default [
{
ignores: ["dist/**", "node_modules/**"],
},
js.configs.recommended,
{
files: ["**/*.ts"],
plugins: {
"@typescript-eslint": tsPlugin,
import: importPlugin,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
globals: {
...globals.node,
},
},
settings: {
"import/resolver": {
typescript: true,
},
},
rules: {
...tsPlugin.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
"no-unused-vars": "off",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
},
},
prettierConfig,
];
+9 -4
View File
@@ -65,7 +65,7 @@
"sideEffects": false,
"scripts": {
"build": "tsup",
"lint-check-eslint": "eslint 'src/**/*.ts*'",
"lint-check-eslint": "eslint .",
"lint-check-knip": "knip",
"test-unit": "vitest --run",
"clean": "rm -rf node_modules dist"
@@ -80,10 +80,15 @@
},
"devDependencies": {
"@bufbuild/protocompile": "^0.0.1",
"@eslint/js": "^10.0.0",
"@types/node": "^24.10.9",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"eslint": "^8.57.1",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"globals": "^17.0.0",
"knip": "^5.82.1",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
+1
View File
@@ -18,6 +18,7 @@
},
"lint-check-eslint": {
"description": "Checks code formatting with ESLint",
"dependsOn": ["^generate"],
"inputs": [
"default"
],
+367 -1541
View File
File diff suppressed because it is too large Load Diff