🔧 chore(hooks): 提交前同步远程并静默无效URL测试日志

This commit is contained in:
Abner
2026-04-18 00:36:30 +08:00
parent ca76df59a2
commit a1fa26dc40
4 changed files with 61 additions and 20 deletions

View File

@@ -290,7 +290,7 @@ open tests/test_simyo_esim.html
- `📝 docs(readme): 更新安装说明`
### 当前 Git 钩子
- `pre-commit`:提交前格式化 staged 文件并执行 JS/JSON 语法检查与智能引号拦截
- `pre-commit`:提交前先同步远程更新(有 upstream 时自动 `git pull --rebase --autostash`),再格式化 staged 文件并执行 JS/JSON 语法检查与智能引号拦截
- `pre-push`:默认快速模式(`pre-commit` + 相关测试),无差异文件时回退全量测试
- `prepare-commit-msg`:提交信息为空时自动填充模板
- `commit-msg`校验提交信息格式emoji + type + 中文描述 + 映射关系)

View File

@@ -278,7 +278,7 @@ Examples:
- `📝 docs(readme): 更新安装说明`
### Current Git Hooks
- `pre-commit`: formats staged files and runs JS/JSON syntax + smart quotes checks
- `pre-commit`: syncs remote updates first (auto `git pull --rebase --autostash` when upstream exists), then formats staged files and runs JS/JSON syntax + smart quotes checks
- `pre-push`: defaults to fast mode (`pre-commit` + related tests), falls back to full tests when no diff files are detected
- `prepare-commit-msg`: auto-fills a template when commit message is empty
- `commit-msg`: validates message format (emoji + type + Chinese summary + mapping)

View File

@@ -71,6 +71,39 @@ function getTouchedProtectedFiles() {
return output.split('\0').filter(Boolean);
}
function getUpstreamRef() {
try {
return git([
'rev-parse',
'--abbrev-ref',
'--symbolic-full-name',
'@{u}'
]).trim();
} catch (_) {
return '';
}
}
function syncWithRemote() {
const upstreamRef = getUpstreamRef();
if (!upstreamRef) {
console.log(' 当前分支未配置 upstream跳过远程同步');
return;
}
console.log(`🔄 提交前同步远程更新(${upstreamRef}...`);
try {
git(['pull', '--rebase', '--autostash', '--quiet'], {
stdio: 'inherit'
});
console.log('✅ 远程更新同步完成');
} catch (_) {
console.error('\n❌ pre-commit 远程同步失败:');
console.error(' - 请先手动执行 `git pull --rebase` 解决冲突后再提交。');
process.exit(1);
}
}
function shouldFormat(relPath) {
const ext = path.extname(relPath).toLowerCase();
if (TEXT_EXTENSIONS.has(ext)) {
@@ -191,6 +224,8 @@ function main() {
process.exit(1);
}
syncWithRemote();
const stagedFiles = getStagedFiles();
if (stagedFiles.length === 0) {
process.exit(0);

View File

@@ -8,7 +8,7 @@ describe('Giffgaff OAuthManager', () => {
describe('generateCodeVerifier()', () => {
it('应该生成有效的 code verifier', () => {
const verifier = OAuthManager.generateCodeVerifier();
expect(verifier).toBeDefined();
expect(typeof verifier).toBe('string');
expect(verifier.length).toBeGreaterThanOrEqual(43);
@@ -20,7 +20,7 @@ describe('Giffgaff OAuthManager', () => {
it('每次生成的 verifier 应该不同', () => {
const verifier1 = OAuthManager.generateCodeVerifier();
const verifier2 = OAuthManager.generateCodeVerifier();
expect(verifier1).not.toBe(verifier2);
});
});
@@ -29,7 +29,7 @@ describe('Giffgaff OAuthManager', () => {
it('应该从 verifier 生成有效的 challenge', async () => {
const verifier = 'test-verifier-string';
const challenge = await OAuthManager.generateCodeChallenge(verifier);
expect(challenge).toBeDefined();
expect(typeof challenge).toBe('string');
// 检查是否为 base64url 编码
@@ -40,7 +40,7 @@ describe('Giffgaff OAuthManager', () => {
describe('generateState()', () => {
it('应该生成有效的 state 参数', () => {
const state = OAuthManager.generateState();
expect(state).toBeDefined();
expect(typeof state).toBe('string');
expect(state.length).toBeGreaterThan(0);
@@ -51,7 +51,7 @@ describe('Giffgaff OAuthManager', () => {
it('每次生成的 state 应该不同', () => {
const state1 = OAuthManager.generateState();
const state2 = OAuthManager.generateState();
expect(state1).not.toBe(state2);
});
});
@@ -60,7 +60,7 @@ describe('Giffgaff OAuthManager', () => {
it('应该构建正确的授权 URL', async () => {
const verifier = 'test-verifier';
const url = await OAuthManager.buildAuthorizationUrl(verifier);
expect(url).toContain('https://id.giffgaff.com/oauth/authorize');
expect(url).toContain('response_type=code');
expect(url).toContain('client_id=');
@@ -76,36 +76,42 @@ describe('Giffgaff OAuthManager', () => {
it('应该从标准 URL 提取授权码', () => {
const callbackUrl = 'https://example.com/callback?code=test-code&state=test-state';
const code = OAuthManager.extractCodeFromCallback(callbackUrl);
expect(code).toBe('test-code');
});
it('应该从 giffgaff:// 协议 URL 提取授权码', () => {
const callbackUrl = 'giffgaff://auth/callback/?code=test-code&state=test-state';
const code = OAuthManager.extractCodeFromCallback(callbackUrl);
expect(code).toBe('test-code');
});
it('应该处理编码的授权码', () => {
const callbackUrl = 'https://example.com/callback?code=test%2Bcode%3D&state=test';
const code = OAuthManager.extractCodeFromCallback(callbackUrl);
expect(code).toBe('test+code=');
});
it('应该在没有授权码时返回 null', () => {
const callbackUrl = 'https://example.com/callback?state=test-state';
const code = OAuthManager.extractCodeFromCallback(callbackUrl);
expect(code).toBeNull();
});
it('应该处理无效的 URL', () => {
const consoleErrorSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
const callbackUrl = 'not-a-valid-url';
const code = OAuthManager.extractCodeFromCallback(callbackUrl);
expect(code).toBeNull();
try {
const code = OAuthManager.extractCodeFromCallback(callbackUrl);
expect(code).toBeNull();
} finally {
consoleErrorSpy.mockRestore();
}
});
});
@@ -120,14 +126,14 @@ describe('Giffgaff OAuthManager', () => {
token_type: 'Bearer',
expires_in: 3600
};
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse
});
const result = await OAuthManager.exchangeToken('test-code', 'test-verifier');
expect(result).toEqual(mockResponse);
expect(global.fetch).toHaveBeenCalledWith(
'/.netlify/functions/giffgaff-token-exchange',
@@ -145,10 +151,10 @@ describe('Giffgaff OAuthManager', () => {
status: 400,
text: async () => 'Invalid grant'
});
await expect(
OAuthManager.exchangeToken('invalid-code', 'test-verifier')
).rejects.toThrow('Token exchange failed: 400 - Invalid grant');
});
});
});
});