Change to use dpdm for circular import scanning (#5788)

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
James Brunton
2026-02-24 23:05:23 +00:00
committed by GitHub
parent 1b3bfaec20
commit eab84a13d0
17 changed files with 200 additions and 2230 deletions

View File

@@ -4,7 +4,6 @@ import eslint from '@eslint/js';
import globals from 'globals';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';
const srcGlobs = [
'src/**/*.{js,mjs,jsx,ts,tsx}',
@@ -78,21 +77,4 @@ export default defineConfig(
}
}
},
// Config for import plugin
{
...importPlugin.flatConfigs.recommended,
...importPlugin.flatConfigs.typescript,
rules: {
// ...importPlugin.flatConfigs.recommended.rules, // Temporarily disabled until codebase conformant
...importPlugin.flatConfigs.typescript.rules,
'import/no-cycle': 'error',
},
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.json',
},
},
},
},
);

File diff suppressed because it is too large Load Diff

View File

@@ -77,7 +77,9 @@
"predev": "npm run generate-icons",
"dev": "vite",
"prebuild": "npm run generate-icons",
"lint": "eslint --max-warnings=0",
"lint": "npm run lint:eslint && npm run lint:cycles",
"lint:eslint": "eslint --max-warnings=0",
"lint:cycles": "dpdm src --circular --no-warning --no-tree --exit-code circular:1",
"build": "vite build",
"preview": "vite preview",
"tauri-dev": "tauri dev --no-watch",
@@ -149,9 +151,8 @@
"@typescript-eslint/parser": "^8.44.1",
"@vitejs/plugin-react-swc": "^4.1.0",
"@vitest/coverage-v8": "^3.2.4",
"dpdm": "^3.14.0",
"eslint": "^9.36.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react-hooks": "^5.2.0",
"jsdom": "^27.0.0",
"license-checker": "^25.0.1",

View File

@@ -1,6 +1,7 @@
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
import apiClient from '@app/services/apiClient';
import { getSimulatedAppConfig } from '@app/testing/serverExperienceSimulations';
import type { AppConfig, AppConfigBootstrapMode } from '@app/types/appConfig';
/**
* Sleep utility for delays
@@ -14,55 +15,7 @@ export interface AppConfigRetryOptions {
initialDelay?: number;
}
export interface AppConfig {
baseUrl?: string;
contextPath?: string;
serverPort?: number;
frontendUrl?: string;
appNameNavbar?: string;
languages?: string[];
defaultLocale?: string;
logoStyle?: 'modern' | 'classic';
enableLogin?: boolean;
showSettingsWhenNoLogin?: boolean;
enableEmailInvites?: boolean;
enableOAuth?: boolean;
enableSaml?: boolean;
isAdmin?: boolean;
enableAlphaFunctionality?: boolean;
enableAnalytics?: boolean | null;
enablePosthog?: boolean | null;
enableScarf?: boolean | null;
enableDesktopInstallSlide?: boolean;
premiumEnabled?: boolean;
premiumKey?: string;
termsAndConditions?: string;
privacyPolicy?: string;
cookiePolicy?: string;
impressum?: string;
accessibilityStatement?: string;
runningProOrHigher?: boolean;
runningEE?: boolean;
license?: string;
SSOAutoLogin?: boolean;
serverCertificateEnabled?: boolean;
enableMobileScanner?: boolean;
mobileScannerConvertToPdf?: boolean;
mobileScannerImageResolution?: string;
mobileScannerPageFormat?: string;
mobileScannerStretchToFit?: boolean;
appVersion?: string;
machineType?: string;
activeSecurity?: boolean;
dependenciesReady?: boolean;
error?: string;
isNewServer?: boolean;
isNewUser?: boolean;
defaultHideUnavailableTools?: boolean;
defaultHideUnavailableConversions?: boolean;
}
export type AppConfigBootstrapMode = 'blocking' | 'non-blocking';
export type { AppConfig, AppConfigBootstrapMode };
interface AppConfigContextValue {
config: AppConfig | null;

View File

@@ -6,10 +6,7 @@ import { downloadFiles } from '@app/utils/downloadUtils';
import { FileId } from '@app/types/file';
import { groupFilesByOriginal } from '@app/utils/fileHistoryUtils';
import { openFilesFromDisk } from '@app/services/openFilesFromDisk';
// Module-level storage for file path mappings (quickKey -> localFilePath)
// Used to pass file paths from Tauri file dialog to FileContext
export const pendingFilePathMappings = new Map<string, string>();
export { pendingFilePathMappings } from '@app/services/pendingFilePathMappings';
// Type for the context value - now contains everything directly
interface FileManagerContextValue {

View File

@@ -1,38 +1,17 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { signatureStorageService, type StorageType } from '@app/services/signatureStorageService';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import type {
SavedSignature,
SavedSignaturePayload,
SavedSignatureType,
SignatureScope,
} from '@app/types/signature';
export const MAX_SAVED_SIGNATURES_BACKEND = 20; // Backend limit per user
export const MAX_SAVED_SIGNATURES_LOCALSTORAGE = 10; // LocalStorage limit
export type SavedSignatureType = 'canvas' | 'image' | 'text';
export type SignatureScope = 'personal' | 'shared' | 'localStorage';
export type SavedSignaturePayload =
| {
type: 'canvas';
dataUrl: string;
}
| {
type: 'image';
dataUrl: string;
}
| {
type: 'text';
dataUrl: string;
signerName: string;
fontFamily: string;
fontSize: number;
textColor: string;
};
export type SavedSignature = SavedSignaturePayload & {
id: string;
label: string;
scope: SignatureScope;
createdAt: number;
updatedAt: number;
};
export type { SavedSignature, SavedSignaturePayload, SavedSignatureType, SignatureScope };
export type AddSignatureResult =
| { success: true; signature: SavedSignature }

View File

@@ -1,5 +1,5 @@
import { openFileDialog } from '@app/services/fileDialogService';
import { pendingFilePathMappings } from '@app/contexts/FileManagerContext';
import { pendingFilePathMappings } from '@app/services/pendingFilePathMappings';
import { getDocumentFileDialogFilter } from '@app/utils/fileDialogUtils';
interface OpenFilesFromDiskOptions {

View File

@@ -0,0 +1,3 @@
// Module-level storage for file path mappings (quickKey -> localFilePath)
// Used to pass file paths from Tauri file dialog to FileManagerContext
export const pendingFilePathMappings = new Map<string, string>();

View File

@@ -1,5 +1,5 @@
import apiClient from '@app/services/apiClient';
import type { SavedSignature } from '@app/hooks/tools/sign/useSavedSignatures';
import type { SavedSignature } from '@app/types/signature';
export type StorageType = 'backend' | 'localStorage';

View File

@@ -0,0 +1,49 @@
export interface AppConfig {
baseUrl?: string;
contextPath?: string;
serverPort?: number;
frontendUrl?: string;
appNameNavbar?: string;
languages?: string[];
defaultLocale?: string;
logoStyle?: 'modern' | 'classic';
enableLogin?: boolean;
showSettingsWhenNoLogin?: boolean;
enableEmailInvites?: boolean;
enableOAuth?: boolean;
enableSaml?: boolean;
isAdmin?: boolean;
enableAlphaFunctionality?: boolean;
enableAnalytics?: boolean | null;
enablePosthog?: boolean | null;
enableScarf?: boolean | null;
enableDesktopInstallSlide?: boolean;
premiumEnabled?: boolean;
premiumKey?: string;
termsAndConditions?: string;
privacyPolicy?: string;
cookiePolicy?: string;
impressum?: string;
accessibilityStatement?: string;
runningProOrHigher?: boolean;
runningEE?: boolean;
license?: string;
SSOAutoLogin?: boolean;
serverCertificateEnabled?: boolean;
enableMobileScanner?: boolean;
mobileScannerConvertToPdf?: boolean;
mobileScannerImageResolution?: string;
mobileScannerPageFormat?: string;
mobileScannerStretchToFit?: boolean;
appVersion?: string;
machineType?: string;
activeSecurity?: boolean;
dependenciesReady?: boolean;
error?: string;
isNewServer?: boolean;
isNewUser?: boolean;
defaultHideUnavailableTools?: boolean;
defaultHideUnavailableConversions?: boolean;
}
export type AppConfigBootstrapMode = 'blocking' | 'non-blocking';

View File

@@ -0,0 +1,28 @@
export type SavedSignatureType = 'canvas' | 'image' | 'text';
export type SignatureScope = 'personal' | 'shared' | 'localStorage';
export type SavedSignaturePayload =
| {
type: 'canvas';
dataUrl: string;
}
| {
type: 'image';
dataUrl: string;
}
| {
type: 'text';
dataUrl: string;
signerName: string;
fontFamily: string;
fontSize: number;
textColor: string;
};
export type SavedSignature = SavedSignaturePayload & {
id: string;
label: string;
scope: SignatureScope;
createdAt: number;
updatedAt: number;
};

View File

@@ -0,0 +1,27 @@
import { invoke } from '@tauri-apps/api/core';
const TOKEN_KEY = 'stirling_jwt';
/**
* Read auth token from any available source (Tauri store or localStorage).
* Kept separate to avoid circular dependencies between auth and backend services.
*/
export async function getAuthTokenFromAnySource(): Promise<string | null> {
// Try Tauri store first
try {
const token = await invoke<string | null>('get_auth_token');
if (token) {
return token;
}
} catch (error) {
console.error('[Desktop AuthTokenStore] Failed to read from Tauri store:', error);
}
// Fallback to localStorage
try {
return localStorage.getItem(TOKEN_KEY);
} catch (error) {
console.error('[Desktop AuthTokenStore] Failed to read from localStorage:', error);
return null;
}
}

View File

@@ -1,6 +1,7 @@
import { invoke } from '@tauri-apps/api/core';
import { fetch } from '@tauri-apps/plugin-http';
import { connectionModeService } from '@app/services/connectionModeService';
import { getAuthTokenFromAnySource } from '@app/services/authTokenStore';
export type BackendStatus = 'stopped' | 'starting' | 'healthy' | 'unhealthy';
@@ -122,8 +123,7 @@ export class TauriBackendService {
*/
private async getAuthToken(): Promise<string | null> {
try {
const { authService } = await import('./authService');
return await authService.getAuthToken();
return await getAuthTokenFromAnySource();
} catch (error) {
console.debug('[TauriBackendService] Failed to get auth token:', error);
return null;

View File

@@ -1,4 +1,4 @@
import { PlanFeature } from '@app/services/licenseService';
import type { PlanFeature } from '@app/types/license';
/**
* Shared plan feature definitions for Stirling PDF Self-Hosted

View File

@@ -2,11 +2,7 @@ import apiClient from '@app/services/apiClient';
import { supabase, isSupabaseConfigured } from '@app/services/supabaseClient';
import { getCheckoutMode } from '@app/utils/protocolDetection';
import { PLAN_FEATURES, PLAN_HIGHLIGHTS } from '@app/constants/planConstants';
export interface PlanFeature {
name: string;
included: boolean;
}
import type { LicenseInfo, PlanFeature } from '@app/types/license';
export interface PlanTier {
id: string;
@@ -69,13 +65,7 @@ export interface LicenseKeyResponse {
plan?: string;
}
export interface LicenseInfo {
licenseType: 'NORMAL' | 'SERVER' | 'ENTERPRISE';
enabled: boolean;
maxUsers: number;
hasKey: boolean;
licenseKey?: string; // The actual license key (for upgrades)
}
export type { LicenseInfo, PlanFeature };
export interface LicenseSaveResponse {
success: boolean;

View File

@@ -1,5 +1,5 @@
import type { AppConfig } from '@app/contexts/AppConfigContext';
import type { LicenseInfo } from '@app/services/licenseService';
import type { AppConfig } from '@app/types/appConfig';
import type { LicenseInfo } from '@app/types/license';
interface WauResponse {
trackingSince: string;
@@ -208,4 +208,3 @@ export function getSimulatedLicenseInfo(): LicenseInfo | null {
}
export const DEV_TESTING_ENABLED = DEV_TESTING_MODE;

View File

@@ -0,0 +1,12 @@
export interface PlanFeature {
name: string;
included: boolean;
}
export interface LicenseInfo {
licenseType: 'NORMAL' | 'SERVER' | 'ENTERPRISE';
enabled: boolean;
maxUsers: number;
hasKey: boolean;
licenseKey?: string;
}