Skip to content

Commit d8ffe8a

Browse files
fix parse email issue in plan file
1 parent aab6ac9 commit d8ffe8a

File tree

2 files changed

+237
-4
lines changed

2 files changed

+237
-4
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import fs from 'fs';
2+
import os from 'os';
3+
import path from 'path';
4+
import { writeSqitchPlan } from '../../../src/files/plan/writer';
5+
import { SqitchRow } from '../../../src/files/types';
6+
7+
describe('writeSqitchPlan', () => {
8+
let tempDir: string;
9+
let outputDir: string;
10+
11+
beforeEach(() => {
12+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgpm-writer-test-'));
13+
outputDir = path.join(tempDir, 'output');
14+
fs.mkdirSync(outputDir, { recursive: true });
15+
});
16+
17+
afterEach(() => {
18+
fs.rmSync(tempDir, { recursive: true, force: true });
19+
});
20+
21+
const createTestRows = (): SqitchRow[] => [
22+
{
23+
deploy: 'schemas/test/schema',
24+
revert: 'schemas/test/schema',
25+
verify: 'schemas/test/schema',
26+
content: 'CREATE SCHEMA test;',
27+
name: 'create_schema',
28+
deps: []
29+
},
30+
{
31+
deploy: 'schemas/test/tables/users/table',
32+
revert: 'schemas/test/tables/users/table',
33+
verify: 'schemas/test/tables/users/table',
34+
content: 'CREATE TABLE test.users (id uuid);',
35+
name: 'create_table',
36+
deps: ['schemas/test/schema']
37+
}
38+
];
39+
40+
it('should write plan file with simple author name', () => {
41+
const rows = createTestRows();
42+
const opts = {
43+
outdir: outputDir,
44+
name: 'test-module',
45+
author: 'John Doe',
46+
replacer: (str: string) => str.replace('constructive-extension-name', 'test-module')
47+
};
48+
49+
writeSqitchPlan(rows, opts);
50+
51+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
52+
expect(fs.existsSync(planPath)).toBe(true);
53+
54+
const content = fs.readFileSync(planPath, 'utf-8');
55+
expect(content).toContain('%project=test-module');
56+
expect(content).toContain('John Doe <John Doe@5b0c196eeb62>');
57+
expect(content).toContain('schemas/test/schema 2017-08-11T08:11:51Z John Doe <John Doe@5b0c196eeb62>');
58+
expect(content).toContain('schemas/test/tables/users/table [schemas/test/schema] 2017-08-11T08:11:51Z John Doe <John Doe@5b0c196eeb62>');
59+
});
60+
61+
it('should parse author with email format correctly', () => {
62+
const rows = createTestRows();
63+
const opts = {
64+
outdir: outputDir,
65+
name: 'test-module',
66+
author: 'Alex Thompson <[email protected]>',
67+
replacer: (str: string) => str.replace('constructive-extension-name', 'test-module')
68+
};
69+
70+
writeSqitchPlan(rows, opts);
71+
72+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
73+
const content = fs.readFileSync(planPath, 'utf-8');
74+
75+
// Should have only ONE email part, not two
76+
expect(content).toContain('Alex Thompson <[email protected]>');
77+
expect(content).not.toContain('Alex Thompson <[email protected]> <');
78+
expect(content).not.toContain('@5b0c196eeb62');
79+
80+
// Verify the format is correct
81+
expect(content).toMatch(/schemas\/test\/schema 2017-08-11T08:11:51Z Alex Thompson <alex.thompson@example.com> # add create_schema/);
82+
});
83+
84+
it('should handle author with email and extra spaces', () => {
85+
const rows = createTestRows();
86+
const opts = {
87+
outdir: outputDir,
88+
name: 'test-module',
89+
author: ' Jane Smith <[email protected]> ',
90+
replacer: (str: string) => str.replace('constructive-extension-name', 'test-module')
91+
};
92+
93+
writeSqitchPlan(rows, opts);
94+
95+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
96+
const content = fs.readFileSync(planPath, 'utf-8');
97+
98+
// Should trim spaces correctly
99+
expect(content).toContain('Jane Smith <[email protected]>');
100+
expect(content).not.toContain(' Jane Smith <[email protected]> ');
101+
});
102+
103+
it('should use default author when not provided', () => {
104+
const rows = createTestRows();
105+
const opts = {
106+
outdir: outputDir,
107+
name: 'test-module',
108+
replacer: (str: string) => str.replace('constructive-extension-name', 'test-module')
109+
};
110+
111+
writeSqitchPlan(rows, opts);
112+
113+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
114+
const content = fs.readFileSync(planPath, 'utf-8');
115+
116+
expect(content).toContain('constructive <constructive@5b0c196eeb62>');
117+
});
118+
119+
it('should handle rows with dependencies correctly', () => {
120+
const rows: SqitchRow[] = [
121+
{
122+
deploy: 'schemas/test/schema',
123+
content: 'CREATE SCHEMA test;',
124+
name: 'create_schema',
125+
deps: []
126+
},
127+
{
128+
deploy: 'schemas/test/tables/users/table',
129+
content: 'CREATE TABLE test.users (id uuid);',
130+
name: 'create_table',
131+
deps: ['schemas/test/schema', 'schemas/test/tables/roles/table']
132+
}
133+
];
134+
135+
const opts = {
136+
outdir: outputDir,
137+
name: 'test-module',
138+
author: 'Test User <[email protected]>',
139+
replacer: (str: string) => str.replace('constructive-extension-name', 'test-module')
140+
};
141+
142+
writeSqitchPlan(rows, opts);
143+
144+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
145+
const content = fs.readFileSync(planPath, 'utf-8');
146+
147+
// First row should not have dependencies bracket
148+
expect(content).toMatch(/^schemas\/test\/schema 2017-08-11T08:11:51Z Test User <test@example.com>/m);
149+
150+
// Second row should have dependencies bracket
151+
expect(content).toMatch(/schemas\/test\/tables\/users\/table \[schemas\/test\/schema schemas\/test\/tables\/roles\/table\] 2017-08-11T08:11:51Z Test User <test@example.com>/);
152+
});
153+
154+
it('should skip duplicate deploy paths', () => {
155+
const rows: SqitchRow[] = [
156+
{
157+
deploy: 'schemas/test/schema',
158+
content: 'CREATE SCHEMA test;',
159+
name: 'create_schema',
160+
deps: []
161+
},
162+
{
163+
deploy: 'schemas/test/schema', // duplicate
164+
content: 'ALTER SCHEMA test;',
165+
name: 'alter_schema',
166+
deps: []
167+
}
168+
];
169+
170+
const opts = {
171+
outdir: outputDir,
172+
name: 'test-module',
173+
author: 'Test User',
174+
replacer: (str: string) => str.replace('constructive-extension-name', 'test-module')
175+
};
176+
177+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
178+
179+
writeSqitchPlan(rows, opts);
180+
181+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
182+
const content = fs.readFileSync(planPath, 'utf-8');
183+
184+
// Should only appear once
185+
const matches = content.match(/schemas\/test\/schema/g);
186+
expect(matches?.length).toBe(1); // Only in the header line, not duplicated
187+
188+
expect(consoleSpy).toHaveBeenCalledWith('DUPLICATE schemas/test/schema');
189+
190+
consoleSpy.mockRestore();
191+
});
192+
193+
it('should apply replacer function to plan content', () => {
194+
const rows = createTestRows();
195+
const opts = {
196+
outdir: outputDir,
197+
name: 'test-module',
198+
author: 'Test User',
199+
replacer: (str: string) => {
200+
return str
201+
.replace(/constructive-extension-name/g, 'test-module')
202+
.replace(/schemas\/test/g, 'schemas/custom');
203+
}
204+
};
205+
206+
writeSqitchPlan(rows, opts);
207+
208+
const planPath = path.join(outputDir, 'test-module', 'pgpm.plan');
209+
const content = fs.readFileSync(planPath, 'utf-8');
210+
211+
expect(content).toContain('%project=test-module');
212+
expect(content).toContain('%uri=test-module');
213+
expect(content).toContain('schemas/custom/schema');
214+
expect(content).toContain('schemas/custom/tables/users/table');
215+
expect(content).not.toContain('constructive-extension-name');
216+
});
217+
});
218+

pgpm/core/src/files/plan/writer.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,23 @@ export function writeSqitchPlan(rows: SqitchRow[], opts: PlanWriteOptions): void
1818
fs.mkdirSync(dir, { recursive: true });
1919

2020
const date = (): string => '2017-08-11T08:11:51Z'; // stubbed timestamp
21-
const author = opts.author || 'constructive';
22-
const email = `${author}@5b0c196eeb62`;
21+
22+
// Parse author string - it might contain email in format "Name <email>"
23+
const authorInput = (opts.author || 'constructive').trim();
24+
let authorName = authorInput;
25+
let authorEmail = '';
26+
27+
// Check if author already contains email in <...> format
28+
const emailMatch = authorInput.match(/^(.+?)\s*<([^>]+)>\s*$/);
29+
if (emailMatch) {
30+
// Author already has email format: "Name <email>"
31+
authorName = emailMatch[1].trim();
32+
authorEmail = emailMatch[2].trim();
33+
} else {
34+
// No email in author, use default format
35+
authorName = authorInput;
36+
authorEmail = `${authorName}@5b0c196eeb62`;
37+
}
2338

2439
const duplicates: Record<string, boolean> = {};
2540

@@ -36,9 +51,9 @@ ${rows
3651
duplicates[row.deploy] = true;
3752
3853
if (row.deps?.length) {
39-
return `${row.deploy} [${row.deps.join(' ')}] ${date()} ${author} <${email}> # add ${row.name}`;
54+
return `${row.deploy} [${row.deps.join(' ')}] ${date()} ${authorName} <${authorEmail}> # add ${row.name}`;
4055
}
41-
return `${row.deploy} ${date()} ${author} <${email}> # add ${row.name}`;
56+
return `${row.deploy} ${date()} ${authorName} <${authorEmail}> # add ${row.name}`;
4257
})
4358
.join('\n')}
4459
`);

0 commit comments

Comments
 (0)