fix: separate client and server utilities to fix build error

Split lib/utils.ts into:
- lib/utils.ts: Client-safe utilities only (no Node.js imports)
- lib/project-utils.ts: Server-only utilities with Node.js imports

This fixes the Next.js build error where client components were importing
modules that contained Node.js dependencies (fs, path).

All tests pass and build succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 21:43:38 +08:00
parent 08f9015101
commit edc4cb770f
6 changed files with 66 additions and 63 deletions

View File

@@ -0,0 +1,44 @@
import { getProjectDisplayName } from '../project-utils';
// Mock fs module
const fs = require('fs');
const path = require('path');
jest.mock('fs');
jest.mock('path');
describe('getProjectDisplayName', () => {
const originalAliases = {
'FT': '外放构建',
'FT_DEV': '开发构建',
};
beforeEach(() => {
// Mock the aliases file
path.join.mockReturnValue('/mock/lib/project-aliases.json');
fs.readFileSync.mockReturnValue(JSON.stringify(originalAliases));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should return display name for aliased project', () => {
expect(getProjectDisplayName('FT')).toBe('外放构建');
});
it('should return directory name when no alias exists', () => {
expect(getProjectDisplayName('UNKNOWN')).toBe('UNKNOWN');
});
it('should return directory name when aliases file is empty', () => {
fs.readFileSync.mockReturnValue('{}');
expect(getProjectDisplayName('FT')).toBe('FT');
});
it('should return directory name when aliases file cannot be read', () => {
fs.readFileSync.mockImplementation(() => {
throw new Error('File not found');
});
expect(getProjectDisplayName('FT')).toBe('FT');
});
});

View File

@@ -1,11 +1,4 @@
import { formatVersion, formatFileSize, formatDateTime, getProjectDisplayName } from '../utils';
// Mock fs module
const fs = require('fs');
jest.mock('fs', () => ({
...jest.requireActual('fs'),
readFileSync: jest.fn(),
}));
import { formatVersion, formatFileSize, formatDateTime } from '../utils';
describe('formatVersion', () => {
test('应该将下划线替换为点号', () => {
@@ -70,39 +63,3 @@ describe('formatDateTime', () => {
expect(formatDateTime(date)).toBe('2024-01-05 09:05');
});
});
describe('getProjectDisplayName', () => {
const originalAliases = {
'FT': '外放构建',
'FT_DEV': '开发构建',
};
beforeEach(() => {
// Mock the aliases file
fs.readFileSync.mockReturnValue(JSON.stringify(originalAliases));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should return display name for aliased project', () => {
expect(getProjectDisplayName('FT')).toBe('外放构建');
});
it('should return directory name when no alias exists', () => {
expect(getProjectDisplayName('UNKNOWN')).toBe('UNKNOWN');
});
it('should return directory name when aliases file is empty', () => {
fs.readFileSync.mockReturnValue('{}');
expect(getProjectDisplayName('FT')).toBe('FT');
});
it('should return directory name when aliases file cannot be read', () => {
fs.readFileSync.mockImplementation(() => {
throw new Error('File not found');
});
expect(getProjectDisplayName('FT')).toBe('FT');
});
});

19
lib/project-utils.ts Normal file
View File

@@ -0,0 +1,19 @@
// lib/project-utils.ts
// Server-only utility functions
import * as fs from 'fs';
import * as path from 'path';
/**
* 获取项目显示名称(从别名配置)
*/
export function getProjectDisplayName(projectName: string): string {
try {
const aliasesPath = path.join(process.cwd(), 'lib', 'project-aliases.json');
const aliasesContent = fs.readFileSync(aliasesPath, 'utf-8');
const aliases = JSON.parse(aliasesContent);
return aliases[projectName] || projectName;
} catch {
// If file doesn't exist or is invalid, return original name
return projectName;
}
}

View File

@@ -1,7 +1,5 @@
// lib/utils.ts
// Client-safe utility functions
import * as fs from 'fs';
import * as path from 'path';
/**
* 格式化版本号显示 (0_42 -> 0.42)
@@ -31,18 +29,3 @@ export function formatDateTime(date: Date): string {
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
/**
* 获取项目显示名称(从别名配置)
*/
export function getProjectDisplayName(projectName: string): string {
try {
const aliasesPath = path.join(process.cwd(), 'lib', 'project-aliases.json');
const aliasesContent = fs.readFileSync(aliasesPath, 'utf-8');
const aliases = JSON.parse(aliasesContent);
return aliases[projectName] || projectName;
} catch {
// If file doesn't exist or is invalid, return original name
return projectName;
}
}