-
Notifications
You must be signed in to change notification settings - Fork 490
Feat/workspaces 5 auth gate check #8350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| import { flushPromises, mount } from '@vue/test-utils' | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest' | ||
| import { ref } from 'vue' | ||
|
|
||
| import WorkspaceAuthGate from './WorkspaceAuthGate.vue' | ||
|
|
||
| const mockIsInitialized = ref(false) | ||
| const mockCurrentUser = ref<object | null>(null) | ||
|
|
||
| vi.mock('@/stores/firebaseAuthStore', () => ({ | ||
| useFirebaseAuthStore: () => ({ | ||
| isInitialized: mockIsInitialized, | ||
| currentUser: mockCurrentUser | ||
| }) | ||
| })) | ||
|
|
||
| const mockRefreshRemoteConfig = vi.fn() | ||
| vi.mock('@/platform/remoteConfig/refreshRemoteConfig', () => ({ | ||
| refreshRemoteConfig: (options: unknown) => mockRefreshRemoteConfig(options) | ||
| })) | ||
|
|
||
| const mockTeamWorkspacesEnabled = vi.hoisted(() => ({ value: false })) | ||
| vi.mock('@/composables/useFeatureFlags', () => ({ | ||
| useFeatureFlags: () => ({ | ||
| flags: { | ||
| get teamWorkspacesEnabled() { | ||
| return mockTeamWorkspacesEnabled.value | ||
| } | ||
| } | ||
| }) | ||
| })) | ||
|
|
||
| const mockWorkspaceStoreInitialize = vi.fn() | ||
| const mockWorkspaceStoreInitState = vi.hoisted(() => ({ | ||
| value: 'uninitialized' as string | ||
| })) | ||
| vi.mock('@/platform/workspace/stores/teamWorkspaceStore', () => ({ | ||
| useTeamWorkspaceStore: () => ({ | ||
| get initState() { | ||
| return mockWorkspaceStoreInitState.value | ||
| }, | ||
| initialize: mockWorkspaceStoreInitialize | ||
| }) | ||
| })) | ||
|
|
||
| const mockIsCloud = vi.hoisted(() => ({ value: true })) | ||
| vi.mock('@/platform/distribution/types', () => ({ | ||
| get isCloud() { | ||
| return mockIsCloud.value | ||
| } | ||
| })) | ||
|
|
||
| vi.mock('primevue/progressspinner', () => ({ | ||
| default: { template: '<div class="progress-spinner" />' } | ||
| })) | ||
|
|
||
| describe('WorkspaceAuthGate', () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks() | ||
| mockIsCloud.value = true | ||
| mockIsInitialized.value = false | ||
| mockCurrentUser.value = null | ||
| mockTeamWorkspacesEnabled.value = false | ||
| mockWorkspaceStoreInitState.value = 'uninitialized' | ||
| mockRefreshRemoteConfig.mockResolvedValue(undefined) | ||
| mockWorkspaceStoreInitialize.mockResolvedValue(undefined) | ||
| }) | ||
|
|
||
| const mountComponent = () => | ||
| mount(WorkspaceAuthGate, { | ||
| slots: { | ||
| default: '<div data-testid="slot-content">App Content</div>' | ||
| } | ||
| }) | ||
|
|
||
| describe('non-cloud builds', () => { | ||
| it('renders slot immediately when isCloud is false', async () => { | ||
| mockIsCloud.value = false | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| expect(wrapper.find('.progress-spinner').exists()).toBe(false) | ||
| expect(mockRefreshRemoteConfig).not.toHaveBeenCalled() | ||
| }) | ||
| }) | ||
|
|
||
| describe('cloud builds - unauthenticated user', () => { | ||
| it('shows spinner while waiting for Firebase auth', () => { | ||
| mockIsInitialized.value = false | ||
|
|
||
| const wrapper = mountComponent() | ||
|
|
||
| expect(wrapper.find('.progress-spinner').exists()).toBe(true) | ||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(false) | ||
| }) | ||
|
|
||
| it('renders slot when Firebase initializes with no user', async () => { | ||
| mockIsInitialized.value = false | ||
|
|
||
| const wrapper = mountComponent() | ||
| expect(wrapper.find('.progress-spinner').exists()).toBe(true) | ||
|
|
||
| mockIsInitialized.value = true | ||
| mockCurrentUser.value = null | ||
| await flushPromises() | ||
|
|
||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| expect(mockRefreshRemoteConfig).not.toHaveBeenCalled() | ||
| }) | ||
| }) | ||
|
|
||
| describe('cloud builds - authenticated user', () => { | ||
| beforeEach(() => { | ||
| mockIsInitialized.value = true | ||
| mockCurrentUser.value = { uid: 'user-123' } | ||
| }) | ||
|
|
||
| it('refreshes remote config with auth after Firebase init', async () => { | ||
| mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(mockRefreshRemoteConfig).toHaveBeenCalledWith({ useAuth: true }) | ||
| }) | ||
|
|
||
| it('renders slot when teamWorkspacesEnabled is false', async () => { | ||
| mockTeamWorkspacesEnabled.value = false | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| expect(mockWorkspaceStoreInitialize).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('initializes workspace store when teamWorkspacesEnabled is true', async () => { | ||
| mockTeamWorkspacesEnabled.value = true | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(mockWorkspaceStoreInitialize).toHaveBeenCalled() | ||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| }) | ||
|
|
||
| it('skips workspace init when store is already initialized', async () => { | ||
| mockTeamWorkspacesEnabled.value = true | ||
| mockWorkspaceStoreInitState.value = 'ready' | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(mockWorkspaceStoreInitialize).not.toHaveBeenCalled() | ||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('error handling - graceful degradation', () => { | ||
| beforeEach(() => { | ||
| mockIsInitialized.value = true | ||
| mockCurrentUser.value = { uid: 'user-123' } | ||
| }) | ||
|
|
||
| it('renders slot when remote config refresh fails', async () => { | ||
| mockRefreshRemoteConfig.mockRejectedValue(new Error('Network error')) | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| }) | ||
|
|
||
| it('renders slot when remote config refresh times out', async () => { | ||
| vi.useFakeTimers() | ||
| // Never-resolving promise simulates a hanging request | ||
| mockRefreshRemoteConfig.mockReturnValue(new Promise(() => {})) | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| // Still showing spinner before timeout | ||
| expect(wrapper.find('.progress-spinner').exists()).toBe(true) | ||
|
|
||
| // Advance past the 10 second timeout | ||
| await vi.advanceTimersByTimeAsync(10_001) | ||
| await flushPromises() | ||
|
|
||
| // Should render slot after timeout (graceful degradation) | ||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| vi.useRealTimers() | ||
| }) | ||
|
|
||
| it('renders slot when workspace store initialization fails', async () => { | ||
| mockTeamWorkspacesEnabled.value = true | ||
| mockWorkspaceStoreInitialize.mockRejectedValue( | ||
| new Error('Workspace init failed') | ||
| ) | ||
|
|
||
| const wrapper = mountComponent() | ||
| await flushPromises() | ||
|
|
||
| expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true) | ||
| }) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| <template> | ||
| <slot v-if="isReady" /> | ||
| <div | ||
| v-else | ||
| class="fixed inset-0 z-[1100] flex items-center justify-center bg-[var(--p-mask-background)]" | ||
| > | ||
| <ProgressSpinner /> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| /** | ||
| * WorkspaceAuthGate - Conditional auth checkpoint for workspace mode. | ||
| * | ||
| * This gate ensures proper initialization order for workspace-scoped auth: | ||
| * 1. Wait for Firebase auth to resolve | ||
| * 2. Check if teamWorkspacesEnabled feature flag is on | ||
| * 3. If YES: Initialize workspace token and store before rendering | ||
| * 4. If NO: Render immediately using existing Firebase auth | ||
| * | ||
| * This prevents race conditions where API calls use Firebase tokens | ||
| * instead of workspace tokens when the workspace feature is enabled. | ||
| */ | ||
| import { promiseTimeout, until } from '@vueuse/core' | ||
| import { storeToRefs } from 'pinia' | ||
| import ProgressSpinner from 'primevue/progressspinner' | ||
| import { ref } from 'vue' | ||
|
|
||
| import { useFeatureFlags } from '@/composables/useFeatureFlags' | ||
| import { isCloud } from '@/platform/distribution/types' | ||
| import { refreshRemoteConfig } from '@/platform/remoteConfig/refreshRemoteConfig' | ||
| import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore' | ||
| import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' | ||
|
|
||
| const FIREBASE_INIT_TIMEOUT_MS = 16_000 | ||
| const CONFIG_REFRESH_TIMEOUT_MS = 10_000 | ||
|
|
||
| const isReady = ref(!isCloud) | ||
|
|
||
| async function initialize(): Promise<void> { | ||
| if (!isCloud) return | ||
|
|
||
| const authStore = useFirebaseAuthStore() | ||
| const { isInitialized, currentUser } = storeToRefs(authStore) | ||
|
|
||
| try { | ||
| // Step 1: Wait for Firebase auth to resolve | ||
| // This is shared with router guard - both wait for the same thing, | ||
| // but this gate blocks rendering while router guard blocks navigation | ||
| if (!isInitialized.value) { | ||
| await until(isInitialized).toBe(true, { | ||
| timeout: FIREBASE_INIT_TIMEOUT_MS | ||
| }) | ||
| } | ||
|
|
||
| // Step 2: If not authenticated, nothing more to do | ||
| // Unauthenticated users don't have workspace context | ||
| if (!currentUser.value) { | ||
| isReady.value = true | ||
| return | ||
| } | ||
|
|
||
| // Step 3: Refresh feature flags with auth context | ||
| // This ensures teamWorkspacesEnabled reflects the authenticated user's state | ||
| // Timeout prevents hanging if server is slow/unresponsive | ||
| try { | ||
| await Promise.race([ | ||
| refreshRemoteConfig({ useAuth: true }), | ||
| promiseTimeout(CONFIG_REFRESH_TIMEOUT_MS).then(() => { | ||
| throw new Error('Config refresh timeout') | ||
| }) | ||
| ]) | ||
| } catch (error) { | ||
| console.warn( | ||
| '[WorkspaceAuthGate] Failed to refresh remote config:', | ||
| error | ||
| ) | ||
| // Continue - feature flags will use defaults (teamWorkspacesEnabled=false) | ||
| // App will render with Firebase auth fallback | ||
| } | ||
|
|
||
| // Step 4: THE CHECKPOINT - Are we in workspace mode? | ||
| const { flags } = useFeatureFlags() | ||
| if (!flags.teamWorkspacesEnabled) { | ||
| // Not in workspace mode - use existing Firebase auth flow | ||
| // No additional initialization needed | ||
| isReady.value = true | ||
| return | ||
| } | ||
|
|
||
| // Step 5: WORKSPACE MODE - Full initialization | ||
| await initializeWorkspaceMode() | ||
| } catch (error) { | ||
| console.error('[WorkspaceAuthGate] Initialization failed:', error) | ||
| } finally { | ||
| // Always render (graceful degradation) | ||
| // If workspace init failed, API calls fall back to Firebase token | ||
| isReady.value = true | ||
| } | ||
| } | ||
|
|
||
| async function initializeWorkspaceMode(): Promise<void> { | ||
| // Initialize the full workspace store which handles: | ||
| // - Restoring workspace token from session (fast path for refresh) | ||
| // - Fetching workspace list | ||
| // - Switching to last used workspace if needed | ||
| // - Setting active workspace | ||
| try { | ||
| const workspaceStore = useTeamWorkspaceStore() | ||
| if (workspaceStore.initState === 'uninitialized') { | ||
| await workspaceStore.initialize() | ||
| } | ||
| } catch (error) { | ||
| // Log but don't block - workspace UI features may not work but app will render | ||
| // API calls will fall back to Firebase token | ||
| console.warn( | ||
| '[WorkspaceAuthGate] Failed to initialize workspace store:', | ||
| error | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Start initialization immediately during component setup | ||
| // (not in onMounted, so initialization starts before DOM is ready) | ||
| void initialize() | ||
| </script> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Comfy-Org/ComfyUI_frontend
Length of output: 2556
🏁 Script executed:
Repository: Comfy-Org/ComfyUI_frontend
Length of output: 8139
🏁 Script executed:
Repository: Comfy-Org/ComfyUI_frontend
Length of output: 781
🏁 Script executed:
Repository: Comfy-Org/ComfyUI_frontend
Length of output: 2601
Replace PrimeVue ProgressSpinner with a custom loading component.
Per coding guidelines, "Avoid new usage of PrimeVue components." Consider using a project-standard loading indicator from
src/components/or implementing a simple spinner alternative.Also, the
z-[1100]hardcoded value should use a design token instead of an arbitrary number to maintain consistency with the theme system and avoid conflicts with other overlays.🧰 Tools
🪛 ESLint
[error] 1-1: Resolve error: EACCES: permission denied, open '/iVKfOXJLLN'
at Object.writeFileSync (node:fs:2409:20)
at l (file:///home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.mjs:7:13670)
at createFilesMatcher (file:///home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.mjs:7:14422)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import-x@4.16.1_@typescript-eslin_44eddb5b99ae4bce470e6fb9a90221ee/node_modules/eslint-import-resolver-typescript/lib/index.js:70:65)
at Object.resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import-x@4.16.1_@typescript-eslin_44eddb5b99ae4bce470e6fb9a90221ee/node_modules/eslint-import-resolver-typescript/lib/index.js:147:20)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:69
at setRuleContext (/home/jailuser/git/node_modules/.pnpm/eslint-import-context@0.1.9_unrs-resolver@1.11.1/node_modules/eslint-import-context/lib/index.js:23:20)
at fullResolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:30)
at relative (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:215:12)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:220:16)
at checkSourceValue (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/rules/no-unresolved.js:31:34)
at checkSourceValue (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:14:9)
at checkSource (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:17:9)
at ruleErrorHandler (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1173:33)
at /home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-visitor.js:76:46
at Array.forEach ()
at SourceCodeVisitor.callSync (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-visitor.js:76:30)
at /home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-traverser.js:291:18
at Array.forEach ()
at SourceCodeTraverser.traverseSync (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-traverser.js:290:10)
at runRules (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1214:12)
at
#flatVerifyWithoutProcessors(/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2101:4)at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2189:43)
at /home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1773:17
at Array.map ()
at Linter._verifyWithFlatConfigArrayAndProcessor (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1768:30)
at Linter._verifyWithFlatConfigArray (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2275:16)
at Linter.verify (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1677:10)
at Linter.verifyAndFix (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2557:20)
at verifyText (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/eslint/eslint-helpers.js:1180:45)
at readAndVerifyFile (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/eslint/eslint-helpers.js:1321:10)
(import-x/no-unresolved)
[error] 1-1: Resolve error: EACCES: permission denied, open '/HOcZIXwqKG'
at Object.writeFileSync (node:fs:2409:20)
at l (file:///home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.mjs:7:13670)
at createFilesMatcher (file:///home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.mjs:7:14422)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import-x@4.16.1_@typescript-eslin_44eddb5b99ae4bce470e6fb9a90221ee/node_modules/eslint-import-resolver-typescript/lib/index.js:70:65)
at Object.resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import-x@4.16.1_@typescript-eslin_44eddb5b99ae4bce470e6fb9a90221ee/node_modules/eslint-import-resolver-typescript/lib/index.js:147:20)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:69
at setRuleContext (/home/jailuser/git/node_modules/.pnpm/eslint-import-context@0.1.9_unrs-resolver@1.11.1/node_modules/eslint-import-context/lib/index.js:23:20)
at fullResolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:30)
at relative (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:215:12)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:220:16)
at importType (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/import-type.js:126:63)
at checkImportForRelativePackage (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/rules/no-relative-packages.js:15:38)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/rules/no-relative-packages.js:59:40
at checkSourceValue (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:14:9)
at checkSource (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint+utils@8.50.0_eslint@9.39.1_jiti@2.6.1__eacac87f98d760f1781d40e8519857dc/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:17:9)
at ruleErrorHandler (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1173:33)
at /home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-visitor.js:76:46
at Array.forEach ()
at SourceCodeVisitor.callSync (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-visitor.js:76:30)
at /home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-traverser.js:291:18
at Array.forEach ()
at SourceCodeTraverser.traverseSync (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/source-code-traverser.js:290:10)
at runRules (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1214:12)
at
#flatVerifyWithoutProcessors(/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2101:4)at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2189:43)
at /home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1773:17
at Array.map ()
at Linter._verifyWithFlatConfigArrayAndProcessor (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1768:30)
at Linter._verifyWithFlatConfigArray (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2275:16)
at Linter.verify (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:1677:10)
at Linter.verifyAndFix (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/linter/linter.js:2557:20)
at verifyText (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/eslint/eslint-helpers.js:1180:45)
at readAndVerifyFile (/home/jailuser/git/node_modules/.pnpm/eslint@9.39.1_jiti@2.6.1/node_modules/eslint/lib/eslint/eslint-helpers.js:1321:10)
(import-x/no-relative-packages)
🤖 Prompt for AI Agents