diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs index 7b29bae59..db11486f8 100644 --- a/app/.eslintrc.cjs +++ b/app/.eslintrc.cjs @@ -54,23 +54,25 @@ module.exports = { { groups: [ // Node.js built-ins - ['^node:'], ['^node:.*/'], - // External packages - ['^[a-zA-Z]'], + // External packages (including @-prefixed external packages) + ['^[a-zA-Z]', '^@(?!selfxyz|/)'], + // Internal workspace packages - ['^@selfxyz/'], - // Internal relative imports + // Internal alias imports (new @/ alias) + ['^@/'], + + // Internal relative imports ['^[./]'], ], }, ], - // Export sorting - using sort-exports for better type prioritization + // Export sorting 'sort-exports/sort-exports': [ 'error', @@ -167,6 +169,20 @@ module.exports = { '@typescript-eslint/indent': 'off', }, overrides: [ + { + // Disable export sorting for files with dependency issues + files: [ + 'src/components/NavBar/BaseNavBar.tsx', + 'src/navigation/index.tsx', + 'src/providers/passportDataProvider.tsx', + 'src/utils/cloudBackup/helpers.ts', + 'src/utils/haptic/index.ts', + 'src/utils/proving/provingUtils.ts', + ], + rules: { + 'sort-exports/sort-exports': 'off', + }, + }, { files: ['tests/**/*.{ts,tsx}'], parserOptions: { @@ -201,19 +217,5 @@ module.exports = { 'no-undef': 'off', }, }, - { - // Disable export sorting for files with dependency issues - files: [ - 'src/components/NavBar/BaseNavBar.tsx', - 'src/navigation/index.tsx', - 'src/providers/passportDataProvider.tsx', - 'src/utils/cloudBackup/helpers.ts', - 'src/utils/haptic/index.ts', - 'src/utils/proving/provingUtils.ts', - ], - rules: { - 'sort-exports/sort-exports': 'off', - }, - }, ], }; diff --git a/app/babel.config.cjs b/app/babel.config.cjs index 1d990eddf..edfd56e5d 100644 --- a/app/babel.config.cjs +++ b/app/babel.config.cjs @@ -5,7 +5,7 @@ module.exports = { 'module-resolver', { root: ['./src'], - alias: { '@src': './src' }, + alias: { '@': './src' }, }, ], ['@babel/plugin-transform-private-methods', { loose: true }], diff --git a/app/jest.config.cjs b/app/jest.config.cjs index aa9053914..eb59dcd1e 100644 --- a/app/jest.config.cjs +++ b/app/jest.config.cjs @@ -9,8 +9,8 @@ module.exports = { moduleNameMapper: { '^@env$': '/tests/__setup__/@env.js', '\\.svg$': '/tests/__setup__/svgMock.js', - '^@src/(.*)$': '/src/$1', - '^@src$': '/src', + '^@/(.*)$': '/src/$1', + '^@$': '/src', '^@tests/(.*)$': '/tests/src/$1', '^@tests$': '/tests/src', }, diff --git a/app/metro.config.cjs b/app/metro.config.cjs index 82987a16c..ea21a203c 100644 --- a/app/metro.config.cjs +++ b/app/metro.config.cjs @@ -13,7 +13,7 @@ const extraNodeModules = { util: require.resolve('util'), assert: require.resolve('assert'), '@babel/runtime': path.join(trueMonorepoNodeModules, '@babel/runtime'), - '@src': path.join(__dirname, 'src'), + '@': path.join(__dirname, 'src'), '@selfxyz/common': path.resolve(commonPath, 'dist'), '@selfxyz/mobile-sdk-alpha': path.resolve(sdkAlphaPath, 'dist'), // Main exports diff --git a/app/scripts/alias-imports.cjs b/app/scripts/alias-imports.cjs index d5996aa88..578413662 100644 --- a/app/scripts/alias-imports.cjs +++ b/app/scripts/alias-imports.cjs @@ -3,30 +3,10 @@ const path = require('node:path'); const { Project, SyntaxKind } = require('ts-morph'); function determineAliasStrategy(dir, abs, baseDir, baseAlias) { - // Always use base alias with path relative to baseDir (no special '@/' handling) const rel = path.relative(baseDir, abs).replace(/\\/g, '/'); return rel ? `${baseAlias}/${rel}` : baseAlias; } -function optimizeExistingSrcImport(spec) { - // Convert @src/path/to/file to @/file if it's a same-directory import - // This migration no longer shortens to '@/' because tooling can't resolve contextual '@/' - if (!spec.startsWith('@src/')) return spec; - return spec; -} - -// Migrate legacy '@/File' (same-directory shorthand) to '@src//' -function migrateAtShorthand(spec, dir, srcDir) { - if (!spec.startsWith('@/')) return spec; - const fileBase = spec.slice(2); // remove '@/' - // Compute path relative to src for the current directory, then append the fileBase - const relFromSrcToCurrentDir = path.relative(srcDir, dir).replace(/\\/g, '/'); - const finalPath = relFromSrcToCurrentDir - ? `@src/${relFromSrcToCurrentDir}/${fileBase}` - : `@src/${fileBase}`; - return finalPath; -} - function transformProjectToAliasImports(project, appRootPath) { const srcDir = path.join(appRootPath, 'src'); const testsDir = path.join(appRootPath, 'tests', 'src'); @@ -39,21 +19,8 @@ function transformProjectToAliasImports(project, appRootPath) { for (const declaration of sourceFile.getImportDeclarations()) { const spec = declaration.getModuleSpecifierValue(); - // Handle existing @src/ imports - keep as-is (no '@/' optimization) - if (spec.startsWith('@src/')) { - const optimized = optimizeExistingSrcImport(spec, dir, srcDir); - if (optimized !== spec) { - declaration.setModuleSpecifier(optimized); - } - continue; - } - - // Handle legacy '@/File' shorthand and migrate it - if (spec.startsWith('@/')) { - const migrated = migrateAtShorthand(spec, dir, srcDir); - if (migrated !== spec) { - declaration.setModuleSpecifier(migrated); - } + // Skip existing alias imports + if (spec.startsWith('@/') || spec.startsWith('@tests/')) { continue; } @@ -67,7 +34,7 @@ function transformProjectToAliasImports(project, appRootPath) { const relFromSrc = path.relative(srcDir, abs); if (!relFromSrc.startsWith('..') && !path.isAbsolute(relFromSrc)) { baseDir = srcDir; - baseAlias = '@src'; + baseAlias = '@'; } else { const relFromTests = path.relative(testsDir, abs); if (!relFromTests.startsWith('..') && !path.isAbsolute(relFromTests)) { @@ -87,21 +54,8 @@ function transformProjectToAliasImports(project, appRootPath) { const spec = declaration.getModuleSpecifierValue(); if (!spec) continue; - // Handle existing @src/ exports - keep as-is - if (spec.startsWith('@src/')) { - const optimized = optimizeExistingSrcImport(spec, dir, srcDir); - if (optimized !== spec) { - declaration.setModuleSpecifier(optimized); - } - continue; - } - - // Handle legacy '@/File' shorthand and migrate it - if (spec.startsWith('@/')) { - const migrated = migrateAtShorthand(spec, dir, srcDir); - if (migrated !== spec) { - declaration.setModuleSpecifier(migrated); - } + // Skip existing alias exports + if (spec.startsWith('@/') || spec.startsWith('@tests/')) { continue; } @@ -114,7 +68,7 @@ function transformProjectToAliasImports(project, appRootPath) { const relFromSrc = path.relative(srcDir, abs); if (!relFromSrc.startsWith('..') && !path.isAbsolute(relFromSrc)) { baseDir = srcDir; - baseAlias = '@src'; + baseAlias = '@'; } else { const relFromTests = path.relative(testsDir, abs); if (!relFromTests.startsWith('..') && !path.isAbsolute(relFromTests)) { @@ -152,21 +106,8 @@ function transformProjectToAliasImports(project, appRootPath) { const spec = arg.getLiteralValue(); - // Handle existing @src/ requires - keep as-is - if (spec.startsWith('@src/')) { - const optimized = optimizeExistingSrcImport(spec, dir, srcDir); - if (optimized !== spec) { - arg.setLiteralValue(optimized); - } - continue; - } - - // Handle legacy '@/File' shorthand and migrate it - if (spec.startsWith('@/')) { - const migrated = migrateAtShorthand(spec, dir, srcDir); - if (migrated !== spec) { - arg.setLiteralValue(migrated); - } + // Skip existing alias requires + if (spec.startsWith('@/') || spec.startsWith('@tests/')) { continue; } @@ -181,7 +122,7 @@ function transformProjectToAliasImports(project, appRootPath) { const relFromSrc = path.relative(srcDir, abs); if (!relFromSrc.startsWith('..') && !path.isAbsolute(relFromSrc)) { baseDir = srcDir; - baseAlias = '@src'; + baseAlias = '@'; } else { const relFromTests = path.relative(testsDir, abs); if (!relFromTests.startsWith('..') && !path.isAbsolute(relFromTests)) { diff --git a/app/scripts/tests/alias-imports.test.cjs b/app/scripts/tests/alias-imports.test.cjs index 35be8bbef..da90585d4 100644 --- a/app/scripts/tests/alias-imports.test.cjs +++ b/app/scripts/tests/alias-imports.test.cjs @@ -58,7 +58,7 @@ describe('alias-imports transform', () => { const b = project.getSourceFileOrThrow(fileB); const imports = b.getImportDeclarations(); assert.strictEqual(imports.length, 1); - assert.strictEqual(imports[0].getModuleSpecifierValue(), '@src/utils/a'); + assert.strictEqual(imports[0].getModuleSpecifierValue(), '@/utils/a'); }); it('transforms relative require to @src alias', () => { @@ -85,7 +85,7 @@ describe('alias-imports transform', () => { transformProjectToAliasImports(project, appRoot); const c = project.getSourceFileOrThrow(fileC); - assert.ok(c.getText().includes("require('@src/utils/x')")); + assert.ok(c.getText().includes("require('@/utils/x')")); }); it('transforms relative TS import in tests to @tests alias', () => { @@ -194,10 +194,7 @@ describe('alias-imports transform', () => { const specFile = project.getSourceFileOrThrow(deepSpecFile); const imports = specFile.getImportDeclarations(); assert.strictEqual(imports.length, 1); - assert.strictEqual( - imports[0].getModuleSpecifierValue(), - '@src/utils/haptic', - ); + assert.strictEqual(imports[0].getModuleSpecifierValue(), '@/utils/haptic'); }); it("transforms deep relative require '../../../src/...' to @src alias from tests", () => { @@ -226,7 +223,7 @@ describe('alias-imports transform', () => { transformProjectToAliasImports(project, appRoot); const specFile = project.getSourceFileOrThrow(deepSpecFile); - assert.ok(specFile.getText().includes("require('@src/utils/haptic')")); + assert.ok(specFile.getText().includes("require('@/utils/haptic')")); }); it('aliases export star re-exports with ../ from sibling directory', () => { @@ -251,7 +248,7 @@ describe('alias-imports transform', () => { const indexFile = project.getSourceFileOrThrow(fileIndex); const exportDecl = indexFile.getExportDeclarations()[0]; - assert.strictEqual(exportDecl.getModuleSpecifierValue(), '@src/utils/a'); + assert.strictEqual(exportDecl.getModuleSpecifierValue(), '@/utils/a'); }); it('aliases export named re-exports with ../ from sibling directory', () => { @@ -276,7 +273,7 @@ describe('alias-imports transform', () => { const indexFile = project.getSourceFileOrThrow(fileIndex); const exportDecl = indexFile.getExportDeclarations()[0]; - assert.strictEqual(exportDecl.getModuleSpecifierValue(), '@src/utils/a'); + assert.strictEqual(exportDecl.getModuleSpecifierValue(), '@/utils/a'); }); it('aliases dynamic import() with relative specifier', () => { @@ -304,7 +301,7 @@ describe('alias-imports transform', () => { const featureFile = project.getSourceFileOrThrow(feature); const text = featureFile.getText(); - assert.ok(text.includes("import('@src/utils/lazy')")); + assert.ok(text.includes("import('@/utils/lazy')")); }); it('aliases jest.mock relative specifier', () => { @@ -332,7 +329,7 @@ describe('alias-imports transform', () => { const featureFile = project.getSourceFileOrThrow(feature); const text = featureFile.getText(); - assert.ok(text.includes("jest.mock('@src/utils/mod')")); + assert.ok(text.includes("jest.mock('@/utils/mod')")); }); it('aliases jest.doMock and jest.unmock relative specifiers', () => { @@ -360,8 +357,8 @@ describe('alias-imports transform', () => { const featureFile = project.getSourceFileOrThrow(feature); const text = featureFile.getText(); - assert.ok(text.includes("jest.doMock('@src/utils/mod2')")); - assert.ok(text.includes("jest.unmock('@src/utils/mod2')")); + assert.ok(text.includes("jest.doMock('@/utils/mod2')")); + assert.ok(text.includes("jest.unmock('@/utils/mod2')")); }); it('aliases relative imports starting with ./', () => { @@ -389,10 +386,150 @@ describe('alias-imports transform', () => { const indexFile = project.getSourceFileOrThrow(index); const importDecl = indexFile.getImportDeclarations()[0]; - // Same-directory imports are migrated to @src// + // Same-directory imports are migrated to @// assert.strictEqual( importDecl.getModuleSpecifierValue(), - '@src/utils/haptic/trigger', + '@/utils/haptic/trigger', ); }); + + describe('Migration functionality', () => { + it('migrates @src/ import to @/', () => { + const appRoot = tempRoot; + const srcDir = path.join(appRoot, 'src'); + const fileA = path.join(srcDir, 'components', 'Button.tsx'); + const fileB = path.join(srcDir, 'utils', 'colors.ts'); + + writeFileEnsured( + fileA, + 'export const Button = () =>
Button
;\n', + ); + writeFileEnsured( + fileB, + "import { Button } from '@src/components/Button';\nexport const colors = { primary: '#007AFF' };\n", + ); + + // Simulate the migration: replace @src/ with @/ + const content = fs.readFileSync(fileB, 'utf8'); + const migratedContent = content.replace(/@src\//g, '@/'); + fs.writeFileSync(fileB, migratedContent, 'utf8'); + + // Verify the migration worked + const finalContent = fs.readFileSync(fileB, 'utf8'); + assert.ok(finalContent.includes("from '@/components/Button'")); + assert.ok(!finalContent.includes('@src/')); + }); + + it('migrates @src/ export to @/', () => { + const appRoot = tempRoot; + const srcDir = path.join(appRoot, 'src'); + const fileA = path.join(srcDir, 'components', 'Button.tsx'); + const fileIndex = path.join(srcDir, 'components', 'index.ts'); + + writeFileEnsured( + fileA, + 'export const Button = () =>
Button
;\n', + ); + writeFileEnsured( + fileIndex, + "export { Button } from '@src/components/Button';\n", + ); + + // Simulate the migration: replace @src/ with @/ + const content = fs.readFileSync(fileIndex, 'utf8'); + const migratedContent = content.replace(/@src\//g, '@/'); + fs.writeFileSync(fileIndex, migratedContent, 'utf8'); + + // Verify the migration worked + const finalContent = fs.readFileSync(fileIndex, 'utf8'); + assert.ok(finalContent.includes("from '@/components/Button'")); + assert.ok(!finalContent.includes('@src/')); + }); + + it('migrates @src/ require to @/', () => { + const appRoot = tempRoot; + const srcDir = path.join(appRoot, 'src'); + const fileA = path.join(srcDir, 'utils', 'colors.ts'); + const fileB = path.join(srcDir, 'components', 'Theme.tsx'); + + writeFileEnsured( + fileA, + 'export const colors = { primary: "#007AFF" };\n', + ); + writeFileEnsured( + fileB, + "const colors = require('@src/utils/colors');\nexport const Theme = () =>
Theme
;\n", + ); + + // Simulate the migration: replace @src/ with @/ + const content = fs.readFileSync(fileB, 'utf8'); + const migratedContent = content.replace(/@src\//g, '@/'); + fs.writeFileSync(fileB, migratedContent, 'utf8'); + + // Verify the migration worked + const finalContent = fs.readFileSync(fileB, 'utf8'); + assert.ok(finalContent.includes("require('@/utils/colors')")); + assert.ok(!finalContent.includes('@src/')); + }); + + it('preserves full paths (no aggressive optimization)', () => { + const appRoot = tempRoot; + const srcDir = path.join(appRoot, 'src'); + const fileA = path.join(srcDir, 'components', 'buttons', 'Button.tsx'); + const fileB = path.join(srcDir, 'screens', 'Home.tsx'); + + writeFileEnsured( + fileA, + 'export const Button = () =>
Button
;\n', + ); + writeFileEnsured( + fileB, + "import { Button } from '@src/components/buttons/Button';\nexport const Home = () =>