gipity 1.0.272 → 1.0.306
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/capture-transcript.test.d.ts +1 -0
- package/dist/__tests__/capture-transcript.test.js +92 -0
- package/dist/__tests__/capture-transcript.test.js.map +1 -0
- package/dist/__tests__/cli-smoke.test.js +5 -5
- package/dist/__tests__/cli-smoke.test.js.map +1 -1
- package/dist/__tests__/prompts.test.d.ts +1 -0
- package/dist/__tests__/prompts.test.js +129 -0
- package/dist/__tests__/prompts.test.js.map +1 -0
- package/dist/__tests__/push-cas.test.d.ts +1 -0
- package/dist/__tests__/push-cas.test.js +133 -0
- package/dist/__tests__/push-cas.test.js.map +1 -0
- package/dist/__tests__/sync-apply.test.d.ts +1 -0
- package/dist/__tests__/sync-apply.test.js +457 -0
- package/dist/__tests__/sync-apply.test.js.map +1 -0
- package/dist/__tests__/sync-lock.test.d.ts +1 -0
- package/dist/__tests__/sync-lock.test.js +105 -0
- package/dist/__tests__/sync-lock.test.js.map +1 -0
- package/dist/__tests__/sync.test.js +115 -151
- package/dist/__tests__/sync.test.js.map +1 -1
- package/dist/adopt-cwd.d.ts +66 -0
- package/dist/adopt-cwd.js +255 -0
- package/dist/adopt-cwd.js.map +1 -0
- package/dist/api.d.ts +11 -1
- package/dist/api.js +46 -3
- package/dist/api.js.map +1 -1
- package/dist/capture/sources/claude-code.d.ts +78 -0
- package/dist/capture/sources/claude-code.js +158 -0
- package/dist/capture/sources/claude-code.js.map +1 -0
- package/dist/commands/chat.js +8 -8
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claude.js +257 -125
- package/dist/commands/claude.js.map +1 -1
- package/dist/commands/init.js +34 -47
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/location.js +1 -1
- package/dist/commands/location.js.map +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/page-inspect.js +36 -25
- package/dist/commands/page-inspect.js.map +1 -1
- package/dist/commands/page-screenshot.d.ts +2 -0
- package/dist/commands/page-screenshot.js +212 -0
- package/dist/commands/page-screenshot.js.map +1 -0
- package/dist/commands/project.js +19 -11
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/relay.js +1 -1
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/scaffold.js +19 -13
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/sync.js +15 -24
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js.map +1 -1
- package/dist/helpers/sync.d.ts +4 -2
- package/dist/helpers/sync.js +13 -7
- package/dist/helpers/sync.js.map +1 -1
- package/dist/hooks/capture-runner.d.ts +24 -0
- package/dist/hooks/capture-runner.js +233 -0
- package/dist/hooks/capture-runner.js.map +1 -0
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/project-setup.d.ts +3 -4
- package/dist/project-setup.js +7 -9
- package/dist/project-setup.js.map +1 -1
- package/dist/prompts.d.ts +7 -0
- package/dist/prompts.js +25 -2
- package/dist/prompts.js.map +1 -1
- package/dist/relay/daemon.d.ts +8 -8
- package/dist/relay/daemon.js +23 -61
- package/dist/relay/daemon.js.map +1 -1
- package/dist/relay/device-http.d.ts +9 -0
- package/dist/relay/device-http.js +58 -0
- package/dist/relay/device-http.js.map +1 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +55 -16
- package/dist/setup.js.map +1 -1
- package/dist/sync.d.ts +55 -37
- package/dist/sync.js +628 -445
- package/dist/sync.js.map +1 -1
- package/dist/upload.d.ts +13 -2
- package/dist/upload.js +59 -6
- package/dist/upload.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sync() integration + baseline + conflict-name tests.
|
|
3
|
+
*
|
|
4
|
+
* Uses `globalThis.fetch` stubbing to intercept HTTP. The tar-download path
|
|
5
|
+
* is not exercised here (node:test lacks clean tar-stream mocking); that's
|
|
6
|
+
* covered end-to-end by the server CAS tests + CLI push CAS test.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { writeFileSync, mkdirSync, mkdtempSync, rmSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { tmpdir } from 'os';
|
|
13
|
+
let home;
|
|
14
|
+
let projectDir;
|
|
15
|
+
let origHome;
|
|
16
|
+
let origCwd;
|
|
17
|
+
let origFetch;
|
|
18
|
+
let origTTY;
|
|
19
|
+
before(() => {
|
|
20
|
+
origHome = process.env.HOME;
|
|
21
|
+
origCwd = process.cwd();
|
|
22
|
+
origFetch = globalThis.fetch;
|
|
23
|
+
origTTY = process.stdout.isTTY;
|
|
24
|
+
home = mkdtempSync(join(tmpdir(), 'gipity-apply-home-'));
|
|
25
|
+
process.env.HOME = home;
|
|
26
|
+
mkdirSync(join(home, '.gipity'), { recursive: true });
|
|
27
|
+
writeFileSync(join(home, '.gipity', 'auth.json'), JSON.stringify({
|
|
28
|
+
accessToken: 'fake-jwt',
|
|
29
|
+
refreshToken: 'fake-refresh',
|
|
30
|
+
email: 'ec-test@914-6.com',
|
|
31
|
+
expiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
|
|
32
|
+
}));
|
|
33
|
+
projectDir = mkdtempSync(join(tmpdir(), 'gipity-apply-proj-'));
|
|
34
|
+
writeFileSync(join(projectDir, '.gipity.json'), JSON.stringify({
|
|
35
|
+
projectGuid: 'proj_apply',
|
|
36
|
+
projectSlug: 'p',
|
|
37
|
+
accountSlug: 'a',
|
|
38
|
+
agentGuid: 'agt_t',
|
|
39
|
+
conversationGuid: null,
|
|
40
|
+
apiBase: 'https://test.invalid',
|
|
41
|
+
ignore: ['.gipity.json', '.gipity/'], // match DEFAULT_SYNC_IGNORE subset for these tests
|
|
42
|
+
}));
|
|
43
|
+
mkdirSync(join(projectDir, '.gipity'), { recursive: true });
|
|
44
|
+
process.chdir(projectDir);
|
|
45
|
+
// Force non-TTY so sync() defaults `interactive: false`.
|
|
46
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, configurable: true });
|
|
47
|
+
});
|
|
48
|
+
after(() => {
|
|
49
|
+
process.chdir(origCwd);
|
|
50
|
+
globalThis.fetch = origFetch;
|
|
51
|
+
if (origHome === undefined)
|
|
52
|
+
delete process.env.HOME;
|
|
53
|
+
else
|
|
54
|
+
process.env.HOME = origHome;
|
|
55
|
+
if (origTTY !== undefined) {
|
|
56
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: origTTY, configurable: true });
|
|
57
|
+
}
|
|
58
|
+
rmSync(home, { recursive: true, force: true });
|
|
59
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
// Nuke everything under projectDir except .gipity.json, then restore an
|
|
63
|
+
// empty .gipity dir. Hermetic is simpler than incremental cleanup.
|
|
64
|
+
const entries = readdirSync(projectDir, { withFileTypes: true });
|
|
65
|
+
for (const e of entries) {
|
|
66
|
+
if (e.name === '.gipity.json')
|
|
67
|
+
continue;
|
|
68
|
+
rmSync(join(projectDir, e.name), { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
mkdirSync(join(projectDir, '.gipity'), { recursive: true });
|
|
71
|
+
});
|
|
72
|
+
function stubFetch(handler) {
|
|
73
|
+
globalThis.fetch = handler;
|
|
74
|
+
}
|
|
75
|
+
// ─── Baseline I/O ────────────────────────────────────────────────
|
|
76
|
+
describe('readBaseline', () => {
|
|
77
|
+
it('returns empty baseline when file missing', async () => {
|
|
78
|
+
const { clearConfigCache } = await import('../config.js');
|
|
79
|
+
clearConfigCache();
|
|
80
|
+
const { readBaseline } = await import('../sync.js');
|
|
81
|
+
const b = readBaseline('proj_apply');
|
|
82
|
+
assert.deepEqual(b.files, {});
|
|
83
|
+
assert.equal(b.lastFullSync, null);
|
|
84
|
+
});
|
|
85
|
+
it('returns empty baseline when file is malformed JSON', async () => {
|
|
86
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), '{ not valid json');
|
|
87
|
+
const { readBaseline } = await import('../sync.js');
|
|
88
|
+
const b = readBaseline('proj_apply');
|
|
89
|
+
assert.deepEqual(b.files, {});
|
|
90
|
+
});
|
|
91
|
+
it('returns empty baseline when projectGuid does not match current project', async () => {
|
|
92
|
+
// Someone else's baseline in our folder — must not leak.
|
|
93
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), JSON.stringify({
|
|
94
|
+
projectGuid: 'proj_OTHER',
|
|
95
|
+
files: { 'leak.txt': { size: 1, mtime: '2024', sha256: 'x', serverVersion: 1 } },
|
|
96
|
+
lastFullSync: null,
|
|
97
|
+
}));
|
|
98
|
+
const { readBaseline } = await import('../sync.js');
|
|
99
|
+
const b = readBaseline('proj_apply');
|
|
100
|
+
assert.deepEqual(b.files, {});
|
|
101
|
+
});
|
|
102
|
+
it('round-trips via writeBaseline', async () => {
|
|
103
|
+
const { readBaseline, writeBaseline } = await import('../sync.js');
|
|
104
|
+
writeBaseline({
|
|
105
|
+
projectGuid: 'proj_apply',
|
|
106
|
+
files: { 'a.ts': { size: 10, mtime: '2024', sha256: 'hhh', serverVersion: 3 } },
|
|
107
|
+
lastFullSync: '2024-01-01T00:00:00.000Z',
|
|
108
|
+
});
|
|
109
|
+
const b = readBaseline('proj_apply');
|
|
110
|
+
assert.equal(b.files['a.ts']?.serverVersion, 3);
|
|
111
|
+
assert.equal(b.lastFullSync, '2024-01-01T00:00:00.000Z');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ─── Conflict filenames ─────────────────────────────────────────
|
|
115
|
+
describe('conflictedCopyName', () => {
|
|
116
|
+
it('inserts hostname+timestamp before the extension', async () => {
|
|
117
|
+
const { conflictedCopyName } = await import('../sync.js');
|
|
118
|
+
const out = conflictedCopyName('src/app.ts');
|
|
119
|
+
assert.match(out, /^src\/app \(conflict from .+ \d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}\)\.ts$/);
|
|
120
|
+
});
|
|
121
|
+
it('handles files with no extension', async () => {
|
|
122
|
+
const { conflictedCopyName } = await import('../sync.js');
|
|
123
|
+
const out = conflictedCopyName('Makefile');
|
|
124
|
+
assert.match(out, /^Makefile \(conflict from .+\)$/);
|
|
125
|
+
});
|
|
126
|
+
it('handles unicode and spaces in the path', async () => {
|
|
127
|
+
const { conflictedCopyName } = await import('../sync.js');
|
|
128
|
+
const out = conflictedCopyName('notes/日本語 file.md');
|
|
129
|
+
assert.match(out, /^notes\/日本語 file \(conflict from /);
|
|
130
|
+
assert.ok(out.endsWith('.md'));
|
|
131
|
+
});
|
|
132
|
+
it('sanitizes weird hostname characters so the filename is safe', async () => {
|
|
133
|
+
const { conflictedCopyName } = await import('../sync.js');
|
|
134
|
+
const out = conflictedCopyName('x.ts');
|
|
135
|
+
// Whatever characters the real hostname had, the result should only
|
|
136
|
+
// contain [A-Za-z0-9._-] in the "from <host>" segment.
|
|
137
|
+
const m = out.match(/\(conflict from ([^ ]+) /);
|
|
138
|
+
assert.ok(m, 'hostname segment present');
|
|
139
|
+
assert.match(m[1], /^[A-Za-z0-9._-]+$/);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
// ─── sync() end-to-end with fetch mock ────────────────────────
|
|
143
|
+
describe('sync() — fetch-intercepted', () => {
|
|
144
|
+
it('noop when local, remote, and baseline all agree', async () => {
|
|
145
|
+
// Baseline says we have foo.txt at v=3, sha=same.
|
|
146
|
+
writeFileSync(join(projectDir, 'foo.txt'), 'content');
|
|
147
|
+
const { createHash } = await import('crypto');
|
|
148
|
+
const sha = createHash('sha256').update('content').digest('hex');
|
|
149
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), JSON.stringify({
|
|
150
|
+
projectGuid: 'proj_apply',
|
|
151
|
+
files: { 'foo.txt': { size: 7, mtime: '2024', sha256: sha, serverVersion: 3 } },
|
|
152
|
+
lastFullSync: null,
|
|
153
|
+
}));
|
|
154
|
+
stubFetch(async (url) => {
|
|
155
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
156
|
+
return new Response(JSON.stringify({ data: [
|
|
157
|
+
{ path: 'foo.txt', size: 7, modified: '2026-04-21', type: 'file', guid: 'fl_foo', contentHash: sha, serverVersion: 3 },
|
|
158
|
+
] }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
159
|
+
}
|
|
160
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
161
|
+
});
|
|
162
|
+
const { clearConfigCache } = await import('../config.js');
|
|
163
|
+
clearConfigCache();
|
|
164
|
+
const { sync, readBaseline } = await import('../sync.js');
|
|
165
|
+
const tBefore = Date.now();
|
|
166
|
+
const result = await sync({ interactive: false });
|
|
167
|
+
assert.equal(result.applied, 0);
|
|
168
|
+
assert.equal(result.plan.actions.length, 0);
|
|
169
|
+
assert.deepEqual(result.errors, []);
|
|
170
|
+
// Baseline's lastFullSync must be bumped even when no actions fired —
|
|
171
|
+
// otherwise we can't tell "sync ran and everything was fine" from
|
|
172
|
+
// "sync never ran".
|
|
173
|
+
const bl = readBaseline('proj_apply');
|
|
174
|
+
assert.ok(bl.lastFullSync, 'lastFullSync must be set');
|
|
175
|
+
assert.ok(new Date(bl.lastFullSync).getTime() >= tBefore, 'lastFullSync must be after the sync call started');
|
|
176
|
+
// Baseline entry preserved.
|
|
177
|
+
assert.equal(bl.files['foo.txt']?.serverVersion, 3);
|
|
178
|
+
});
|
|
179
|
+
it('uploads a new local file with expected=null, baseline updates', async () => {
|
|
180
|
+
writeFileSync(join(projectDir, 'hello.txt'), 'hi');
|
|
181
|
+
let initCallCount = 0;
|
|
182
|
+
let completeCallCount = 0;
|
|
183
|
+
let lastInitBody = null;
|
|
184
|
+
let lastCompleteBody = null;
|
|
185
|
+
stubFetch(async (url, init) => {
|
|
186
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
187
|
+
return new Response(JSON.stringify({ data: [] }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
188
|
+
}
|
|
189
|
+
if (url.includes('/files/upload-init')) {
|
|
190
|
+
initCallCount++;
|
|
191
|
+
lastInitBody = JSON.parse(init.body);
|
|
192
|
+
return new Response(JSON.stringify({ data: {
|
|
193
|
+
upload_guid: 'fl_new', method: 'PUT',
|
|
194
|
+
url: 'https://s3.example/stage', expires_in: 3600,
|
|
195
|
+
} }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
196
|
+
}
|
|
197
|
+
if (url.startsWith('https://s3.example')) {
|
|
198
|
+
return new Response('', { status: 200, headers: { etag: '"fake"' } });
|
|
199
|
+
}
|
|
200
|
+
if (url.includes('/files/upload-complete')) {
|
|
201
|
+
completeCallCount++;
|
|
202
|
+
lastCompleteBody = JSON.parse(init.body);
|
|
203
|
+
return new Response(JSON.stringify({ data: {
|
|
204
|
+
size: 2, guid: 'fl_new', version: 1, server_version: 1,
|
|
205
|
+
} }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
206
|
+
}
|
|
207
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
208
|
+
});
|
|
209
|
+
const { clearConfigCache } = await import('../config.js');
|
|
210
|
+
clearConfigCache();
|
|
211
|
+
const { sync, readBaseline } = await import('../sync.js');
|
|
212
|
+
const result = await sync({ interactive: false });
|
|
213
|
+
assert.equal(result.applied, 1);
|
|
214
|
+
assert.equal(result.plan.uploads, 1);
|
|
215
|
+
assert.equal(initCallCount, 1);
|
|
216
|
+
assert.equal(completeCallCount, 1);
|
|
217
|
+
assert.equal(lastInitBody.expected_server_version, null, 'new file → expected=null');
|
|
218
|
+
assert.equal(lastCompleteBody.expected_server_version, null);
|
|
219
|
+
const bl = readBaseline('proj_apply');
|
|
220
|
+
assert.equal(bl.files['hello.txt']?.serverVersion, 1);
|
|
221
|
+
});
|
|
222
|
+
it('skips deletes in non-interactive mode when the bulk-delete threshold trips', async () => {
|
|
223
|
+
// Seed baseline with 20 files; local has none; remote has none.
|
|
224
|
+
// That's 20 delete-remote actions → blocked by guard in non-interactive.
|
|
225
|
+
const files = {};
|
|
226
|
+
for (let i = 0; i < 20; i++) {
|
|
227
|
+
files[`f${i}.txt`] = { size: 1, mtime: '2024', sha256: `sha${i}`, serverVersion: 1 };
|
|
228
|
+
}
|
|
229
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), JSON.stringify({
|
|
230
|
+
projectGuid: 'proj_apply', files, lastFullSync: null,
|
|
231
|
+
}));
|
|
232
|
+
stubFetch(async (url) => {
|
|
233
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
234
|
+
// Remote has all 20 files (baseline state) — client deleted all 20 locally.
|
|
235
|
+
const data = Object.entries(files).map(([path, e]) => ({
|
|
236
|
+
path, size: 1, modified: '2024', type: 'file',
|
|
237
|
+
guid: `fl_${path}`, contentHash: e.sha256, serverVersion: e.serverVersion,
|
|
238
|
+
}));
|
|
239
|
+
return new Response(JSON.stringify({ data }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
240
|
+
}
|
|
241
|
+
throw new Error(`guard should prevent any DELETE calls, got: ${url}`);
|
|
242
|
+
});
|
|
243
|
+
const { clearConfigCache } = await import('../config.js');
|
|
244
|
+
clearConfigCache();
|
|
245
|
+
const { sync } = await import('../sync.js');
|
|
246
|
+
const result = await sync({ interactive: false });
|
|
247
|
+
assert.equal(result.plan.deletesRemote, 20);
|
|
248
|
+
assert.equal(result.applied, 0, 'no deletes executed under guard');
|
|
249
|
+
assert.equal(result.skipped, 20, 'all 20 actions skipped by guard');
|
|
250
|
+
});
|
|
251
|
+
it('force:true bypasses the bulk-delete guard', async () => {
|
|
252
|
+
const files = {};
|
|
253
|
+
for (let i = 0; i < 20; i++) {
|
|
254
|
+
files[`f${i}.txt`] = { size: 1, mtime: '2024', sha256: `sha${i}`, serverVersion: 1 };
|
|
255
|
+
}
|
|
256
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), JSON.stringify({
|
|
257
|
+
projectGuid: 'proj_apply', files, lastFullSync: null,
|
|
258
|
+
}));
|
|
259
|
+
let deleteCount = 0;
|
|
260
|
+
stubFetch(async (url, init) => {
|
|
261
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
262
|
+
const data = Object.entries(files).map(([path, e]) => ({
|
|
263
|
+
path, size: 1, modified: '2024', type: 'file',
|
|
264
|
+
guid: `fl_${path}`, contentHash: e.sha256, serverVersion: e.serverVersion,
|
|
265
|
+
}));
|
|
266
|
+
return new Response(JSON.stringify({ data }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
267
|
+
}
|
|
268
|
+
if (init?.method === 'DELETE') {
|
|
269
|
+
deleteCount++;
|
|
270
|
+
return new Response(JSON.stringify({ success: true }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
271
|
+
}
|
|
272
|
+
throw new Error(`unexpected: ${url}`);
|
|
273
|
+
});
|
|
274
|
+
const { clearConfigCache } = await import('../config.js');
|
|
275
|
+
clearConfigCache();
|
|
276
|
+
const { sync } = await import('../sync.js');
|
|
277
|
+
const result = await sync({ interactive: false, force: true });
|
|
278
|
+
assert.equal(deleteCount, 20);
|
|
279
|
+
assert.equal(result.applied, 20);
|
|
280
|
+
assert.equal(result.skipped, 0);
|
|
281
|
+
});
|
|
282
|
+
it('plan-only mode returns the plan without any HTTP writes', async () => {
|
|
283
|
+
writeFileSync(join(projectDir, 'new.txt'), 'x');
|
|
284
|
+
let writeCalls = 0;
|
|
285
|
+
stubFetch(async (url, init) => {
|
|
286
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
287
|
+
return new Response(JSON.stringify({ data: [] }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
288
|
+
}
|
|
289
|
+
if (init?.method === 'POST' || init?.method === 'DELETE') {
|
|
290
|
+
writeCalls++;
|
|
291
|
+
}
|
|
292
|
+
throw new Error(`unexpected: ${url} ${init?.method}`);
|
|
293
|
+
});
|
|
294
|
+
const { clearConfigCache } = await import('../config.js');
|
|
295
|
+
clearConfigCache();
|
|
296
|
+
const { sync } = await import('../sync.js');
|
|
297
|
+
const result = await sync({ plan: true, interactive: false });
|
|
298
|
+
assert.equal(writeCalls, 0, 'plan mode must not write');
|
|
299
|
+
assert.equal(result.applied, 0);
|
|
300
|
+
assert.equal(result.plan.uploads, 1);
|
|
301
|
+
assert.ok(result.summary.includes('1 upload'));
|
|
302
|
+
});
|
|
303
|
+
it('plan-time conflict (modified × modified) → rename local + download + upload copy', async () => {
|
|
304
|
+
// Baseline says we have app.js at v=1. Local has edited it. Remote has
|
|
305
|
+
// also been modified (v=2). Upload of app.js returns 409 with current=2.
|
|
306
|
+
const { createHash } = await import('crypto');
|
|
307
|
+
const localSha = createHash('sha256').update('local-edit').digest('hex');
|
|
308
|
+
const remoteSha = createHash('sha256').update('remote-edit').digest('hex');
|
|
309
|
+
writeFileSync(join(projectDir, 'app.js'), 'local-edit');
|
|
310
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), JSON.stringify({
|
|
311
|
+
projectGuid: 'proj_apply',
|
|
312
|
+
files: { 'app.js': {
|
|
313
|
+
size: 'baseline'.length, mtime: '2024',
|
|
314
|
+
sha256: createHash('sha256').update('baseline').digest('hex'),
|
|
315
|
+
serverVersion: 1,
|
|
316
|
+
} },
|
|
317
|
+
lastFullSync: null,
|
|
318
|
+
}));
|
|
319
|
+
// The plan for app.js is 'modified × modified' → conflict (no 409 needed;
|
|
320
|
+
// plan already classified it). But we'll also verify the server-side
|
|
321
|
+
// matching by returning remote_sha differing from baseline.
|
|
322
|
+
const tarHeader = Buffer.from(''); // not used because conflict is detected pre-apply
|
|
323
|
+
let completeCalls = [];
|
|
324
|
+
stubFetch(async (url, init) => {
|
|
325
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
326
|
+
return new Response(JSON.stringify({ data: [
|
|
327
|
+
{ path: 'app.js', size: 11, modified: '2024', type: 'file',
|
|
328
|
+
guid: 'fl_app', contentHash: remoteSha, serverVersion: 2 },
|
|
329
|
+
] }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
330
|
+
}
|
|
331
|
+
if (url.includes('/files/tree?content=tar')) {
|
|
332
|
+
// Build a minimal tar with app.js = 'remote-edit'
|
|
333
|
+
const tar = await import('tar-stream');
|
|
334
|
+
const pack = tar.pack();
|
|
335
|
+
pack.entry({ name: 'app.js' }, 'remote-edit');
|
|
336
|
+
pack.finalize();
|
|
337
|
+
const chunks = [];
|
|
338
|
+
for await (const c of pack)
|
|
339
|
+
chunks.push(c);
|
|
340
|
+
return new Response(Buffer.concat(chunks), { status: 200 });
|
|
341
|
+
}
|
|
342
|
+
if (url.includes('/files/upload-init')) {
|
|
343
|
+
const body = JSON.parse(init.body);
|
|
344
|
+
return new Response(JSON.stringify({ data: {
|
|
345
|
+
upload_guid: `fl_${body.path.length}`, method: 'PUT',
|
|
346
|
+
url: 'https://s3.example/stage', expires_in: 3600,
|
|
347
|
+
} }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
348
|
+
}
|
|
349
|
+
if (url.startsWith('https://s3.example')) {
|
|
350
|
+
return new Response('', { status: 200, headers: { etag: '"fake"' } });
|
|
351
|
+
}
|
|
352
|
+
if (url.includes('/files/upload-complete')) {
|
|
353
|
+
const body = JSON.parse(init.body);
|
|
354
|
+
completeCalls.push(body);
|
|
355
|
+
return new Response(JSON.stringify({ data: {
|
|
356
|
+
size: 10, guid: 'fl_done', version: 1, server_version: 7,
|
|
357
|
+
} }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
358
|
+
}
|
|
359
|
+
throw new Error(`unexpected: ${url}`);
|
|
360
|
+
});
|
|
361
|
+
const { clearConfigCache } = await import('../config.js');
|
|
362
|
+
clearConfigCache();
|
|
363
|
+
const { sync } = await import('../sync.js');
|
|
364
|
+
const result = await sync({ interactive: false });
|
|
365
|
+
assert.equal(result.plan.conflicts, 1);
|
|
366
|
+
// Original path should now hold remote bytes.
|
|
367
|
+
assert.equal(readFileSync(join(projectDir, 'app.js'), 'utf-8'), 'remote-edit');
|
|
368
|
+
// A renamed conflict copy should exist.
|
|
369
|
+
const entries = readdirSync(projectDir);
|
|
370
|
+
const copy = entries.find((n) => n.startsWith('app ') && n.includes('conflict from'));
|
|
371
|
+
assert.ok(copy, 'conflicted-copy file should exist on disk');
|
|
372
|
+
assert.equal(readFileSync(join(projectDir, copy), 'utf-8'), 'local-edit');
|
|
373
|
+
// Both server paths were uploaded (only the renamed copy, since original just downloaded)
|
|
374
|
+
assert.ok(completeCalls.length >= 1, 'at least one upload-complete for the conflict copy');
|
|
375
|
+
});
|
|
376
|
+
it('apply-time 409 (baseline fresh but another client raced in between) → re-plans as conflict', async () => {
|
|
377
|
+
// Plan sees: local='modified', remote='unchanged' → upload with CAS=baseline.
|
|
378
|
+
// But server has already moved on between manifest-fetch and upload — returns
|
|
379
|
+
// 409. This exercises the apply-phase UploadConflictError handler in sync.ts.
|
|
380
|
+
const { createHash } = await import('crypto');
|
|
381
|
+
const baselineSha = createHash('sha256').update('base').digest('hex');
|
|
382
|
+
const localSha = createHash('sha256').update('local-new').digest('hex');
|
|
383
|
+
const newerRemoteSha = createHash('sha256').update('newer-remote').digest('hex');
|
|
384
|
+
writeFileSync(join(projectDir, 'race.txt'), 'local-new');
|
|
385
|
+
writeFileSync(join(projectDir, '.gipity', 'sync-state.json'), JSON.stringify({
|
|
386
|
+
projectGuid: 'proj_apply',
|
|
387
|
+
files: { 'race.txt': {
|
|
388
|
+
size: 4, mtime: '2024', sha256: baselineSha, serverVersion: 3,
|
|
389
|
+
} },
|
|
390
|
+
lastFullSync: null,
|
|
391
|
+
}));
|
|
392
|
+
let initCalls = 0;
|
|
393
|
+
stubFetch(async (url, init) => {
|
|
394
|
+
// Manifest still shows the "unchanged" remote that matches baseline —
|
|
395
|
+
// client will plan an upload with expected=3.
|
|
396
|
+
if (url.includes('/files/tree') && !url.includes('content=tar')) {
|
|
397
|
+
return new Response(JSON.stringify({ data: [
|
|
398
|
+
{ path: 'race.txt', size: 4, modified: '2024', type: 'file',
|
|
399
|
+
guid: 'fl_r', contentHash: baselineSha, serverVersion: 3 },
|
|
400
|
+
] }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
401
|
+
}
|
|
402
|
+
// Between plan and apply, another client bumped the version to 5.
|
|
403
|
+
if (url.includes('/files/upload-init')) {
|
|
404
|
+
initCalls++;
|
|
405
|
+
const body = JSON.parse(init.body);
|
|
406
|
+
if (body.path === 'race.txt' && body.expected_server_version === 3) {
|
|
407
|
+
// Server has moved past 3 — return 409.
|
|
408
|
+
return new Response(JSON.stringify({
|
|
409
|
+
error: { code: 'CONFLICT', message: 'Version mismatch: expected 3, current 5' },
|
|
410
|
+
data: { current_server_version: 5 },
|
|
411
|
+
}), { status: 409, headers: { 'content-type': 'application/json' } });
|
|
412
|
+
}
|
|
413
|
+
// The conflict-copy re-upload (expected=null) succeeds.
|
|
414
|
+
return new Response(JSON.stringify({ data: {
|
|
415
|
+
upload_guid: 'fl_copy', method: 'PUT',
|
|
416
|
+
url: 'https://s3.example/stage', expires_in: 3600,
|
|
417
|
+
} }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
418
|
+
}
|
|
419
|
+
// fetchOne (targeted tar download for the new remote version)
|
|
420
|
+
if (url.includes('/files/tree?content=tar')) {
|
|
421
|
+
const tar = await import('tar-stream');
|
|
422
|
+
const pack = tar.pack();
|
|
423
|
+
pack.entry({ name: 'race.txt' }, 'newer-remote');
|
|
424
|
+
pack.finalize();
|
|
425
|
+
const chunks = [];
|
|
426
|
+
for await (const c of pack)
|
|
427
|
+
chunks.push(c);
|
|
428
|
+
return new Response(Buffer.concat(chunks), { status: 200 });
|
|
429
|
+
}
|
|
430
|
+
if (url.startsWith('https://s3.example')) {
|
|
431
|
+
return new Response('', { status: 200, headers: { etag: '"fake"' } });
|
|
432
|
+
}
|
|
433
|
+
if (url.includes('/files/upload-complete')) {
|
|
434
|
+
return new Response(JSON.stringify({ data: {
|
|
435
|
+
size: 8, guid: 'fl_cc', version: 1, server_version: 1,
|
|
436
|
+
} }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
437
|
+
}
|
|
438
|
+
throw new Error(`unexpected: ${url}`);
|
|
439
|
+
});
|
|
440
|
+
const { clearConfigCache } = await import('../config.js');
|
|
441
|
+
clearConfigCache();
|
|
442
|
+
const { sync } = await import('../sync.js');
|
|
443
|
+
const result = await sync({ interactive: false });
|
|
444
|
+
// Plan thought it was a clean upload (not a plan-level conflict).
|
|
445
|
+
assert.equal(result.plan.conflicts, 0, 'plan classified as upload, not conflict');
|
|
446
|
+
assert.equal(result.plan.uploads, 1);
|
|
447
|
+
assert.ok(initCalls >= 2, 'at least two upload-inits: original (409) + conflict-copy');
|
|
448
|
+
// After 409 handling: original path must hold the newer remote bytes.
|
|
449
|
+
assert.equal(readFileSync(join(projectDir, 'race.txt'), 'utf-8'), 'newer-remote');
|
|
450
|
+
// A conflicted-copy file must exist with the local-new bytes preserved.
|
|
451
|
+
const entries = readdirSync(projectDir);
|
|
452
|
+
const copy = entries.find(n => n.startsWith('race ') && n.includes('conflict from'));
|
|
453
|
+
assert.ok(copy, 'conflicted-copy file should exist on disk');
|
|
454
|
+
assert.equal(readFileSync(join(projectDir, copy), 'utf-8'), 'local-new');
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
//# sourceMappingURL=sync-apply.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-apply.test.js","sourceRoot":"","sources":["../../src/__tests__/sync-apply.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory sync lock (`.gipity/sync.lock`) — prevents concurrent sync
|
|
3
|
+
* processes in the same project dir from corrupting the baseline manifest.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync, rmSync, mkdtempSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
// Import the module under test with a throwaway HOME and cwd so it uses our
|
|
11
|
+
// temp project dir. Note: `acquireLock` reads the path via getConfigPath(),
|
|
12
|
+
// which walks up from cwd looking for .gipity.json.
|
|
13
|
+
let tempProject;
|
|
14
|
+
let originalCwd;
|
|
15
|
+
before(() => {
|
|
16
|
+
originalCwd = process.cwd();
|
|
17
|
+
tempProject = mkdtempSync(join(tmpdir(), 'gipity-lock-test-'));
|
|
18
|
+
writeFileSync(join(tempProject, '.gipity.json'), JSON.stringify({
|
|
19
|
+
projectGuid: 'proj_lock_test',
|
|
20
|
+
projectSlug: 'lock-test',
|
|
21
|
+
accountSlug: 'test',
|
|
22
|
+
agentGuid: 'agt_test',
|
|
23
|
+
conversationGuid: null,
|
|
24
|
+
apiBase: 'https://test.invalid',
|
|
25
|
+
ignore: [],
|
|
26
|
+
}));
|
|
27
|
+
mkdirSync(join(tempProject, '.gipity'), { recursive: true });
|
|
28
|
+
process.chdir(tempProject);
|
|
29
|
+
});
|
|
30
|
+
after(() => {
|
|
31
|
+
process.chdir(originalCwd);
|
|
32
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
// Clean up any lock file between tests.
|
|
36
|
+
try {
|
|
37
|
+
rmSync(join(tempProject, '.gipity', 'sync.lock'), { force: true });
|
|
38
|
+
}
|
|
39
|
+
catch { /* */ }
|
|
40
|
+
});
|
|
41
|
+
describe('acquireLock', () => {
|
|
42
|
+
it('creates the lock file with the current PID', async () => {
|
|
43
|
+
// Re-import so config cache is fresh with our tempProject cwd.
|
|
44
|
+
const { clearConfigCache } = await import('../config.js');
|
|
45
|
+
clearConfigCache();
|
|
46
|
+
const { acquireLock } = await import('../sync.js');
|
|
47
|
+
const release = await acquireLock();
|
|
48
|
+
const lockFile = join(tempProject, '.gipity', 'sync.lock');
|
|
49
|
+
assert.ok(existsSync(lockFile), 'lock file should exist');
|
|
50
|
+
assert.equal(parseInt(readFileSync(lockFile, 'utf-8'), 10), process.pid);
|
|
51
|
+
release();
|
|
52
|
+
assert.ok(!existsSync(lockFile), 'lock file should be removed by release');
|
|
53
|
+
});
|
|
54
|
+
it('release is idempotent (safe to call twice, lockfile stays absent)', async () => {
|
|
55
|
+
const lockFile = join(tempProject, '.gipity', 'sync.lock');
|
|
56
|
+
const { acquireLock } = await import('../sync.js');
|
|
57
|
+
const release = await acquireLock();
|
|
58
|
+
assert.ok(existsSync(lockFile), 'precondition: lockfile exists after acquire');
|
|
59
|
+
release();
|
|
60
|
+
assert.ok(!existsSync(lockFile), 'lockfile gone after first release');
|
|
61
|
+
release(); // Must not throw
|
|
62
|
+
assert.ok(!existsSync(lockFile), 'lockfile stays absent after second release');
|
|
63
|
+
});
|
|
64
|
+
it('breaks a stale lock whose PID is dead', async () => {
|
|
65
|
+
const lockFile = join(tempProject, '.gipity', 'sync.lock');
|
|
66
|
+
// PID 1 exists but is init — but PID 999999 is almost certainly dead.
|
|
67
|
+
// Find a definitely-dead PID by forking a process that exits instantly.
|
|
68
|
+
const { execSync } = await import('child_process');
|
|
69
|
+
const deadPid = parseInt(execSync('bash -c "(echo $$; exec sleep 0.01) & wait $!; echo $!"', { encoding: 'utf-8' })
|
|
70
|
+
.split('\n').filter(Boolean).pop(), 10);
|
|
71
|
+
// Poll briefly to ensure the PID is free.
|
|
72
|
+
let tries = 20;
|
|
73
|
+
while (tries-- > 0) {
|
|
74
|
+
try {
|
|
75
|
+
process.kill(deadPid, 0);
|
|
76
|
+
await new Promise(r => setTimeout(r, 10));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
writeFileSync(lockFile, String(deadPid));
|
|
83
|
+
const { acquireLock } = await import('../sync.js');
|
|
84
|
+
const release = await acquireLock(); // should break the stale lock
|
|
85
|
+
assert.equal(parseInt(readFileSync(lockFile, 'utf-8'), 10), process.pid);
|
|
86
|
+
release();
|
|
87
|
+
});
|
|
88
|
+
it('waits for a live lock, then acquires when it releases', async () => {
|
|
89
|
+
const { acquireLock } = await import('../sync.js');
|
|
90
|
+
// First holder.
|
|
91
|
+
const releaseA = await acquireLock();
|
|
92
|
+
// Second caller must wait — start its promise, then release A, then await.
|
|
93
|
+
let bResolved = false;
|
|
94
|
+
const pB = acquireLock().then(r => { bResolved = true; return r; });
|
|
95
|
+
// Give B a moment to start polling — it should NOT be resolved yet.
|
|
96
|
+
await new Promise(r => setTimeout(r, 100));
|
|
97
|
+
assert.equal(bResolved, false, 'second acquireLock should still be waiting');
|
|
98
|
+
releaseA();
|
|
99
|
+
// Poll cycle is 500ms; wait up to 1.5s for the wait loop to notice.
|
|
100
|
+
const releaseB = await pB;
|
|
101
|
+
assert.equal(bResolved, true);
|
|
102
|
+
releaseB();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=sync-lock.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-lock.test.js","sourceRoot":"","sources":["../../src/__tests__/sync-lock.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAC7F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAE5B,4EAA4E;AAC5E,4EAA4E;AAC5E,oDAAoD;AACpD,IAAI,WAAmB,CAAC;AACxB,IAAI,WAAmB,CAAC;AAExB,MAAM,CAAC,GAAG,EAAE;IACV,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC5B,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAC/D,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;QAC9D,WAAW,EAAE,gBAAgB;QAC7B,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,MAAM;QACnB,SAAS,EAAE,UAAU;QACrB,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE,sBAAsB;QAC/B,MAAM,EAAE,EAAE;KACX,CAAC,CAAC,CAAC;IACJ,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,KAAK,CAAC,GAAG,EAAE;IACT,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,MAAM,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,EAAE;IACd,wCAAwC;IACxC,IAAI,CAAC;QAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;AAC7F,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,+DAA+D;QAC/D,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QAC1D,gBAAgB,EAAE,CAAC;QACnB,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAEnD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAC3D,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACzE,OAAO,EAAE,CAAC;QACV,MAAM,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,wCAAwC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAC3D,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;QACpC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,6CAA6C,CAAC,CAAC;QAC/E,OAAO,EAAE,CAAC;QACV,MAAM,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,mCAAmC,CAAC,CAAC;QACtE,OAAO,EAAE,CAAC,CAAE,iBAAiB;QAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,4CAA4C,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAC3D,sEAAsE;QACtE,wEAAwE;QACxE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,QAAQ,CACtB,QAAQ,CAAC,yDAAyD,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;aACvF,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAG,EACrC,EAAE,CACH,CAAC;QACF,0CAA0C;QAC1C,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,OAAO,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC;gBAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAAC,CAAC;YAC5E,MAAM,CAAC;gBAAC,MAAM;YAAC,CAAC;QAClB,CAAC;QAED,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QACzC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC,CAAE,8BAA8B;QACpE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACzE,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAEnD,gBAAgB;QAChB,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;QAErC,2EAA2E;QAC3E,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,EAAE,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpE,oEAAoE;QACpE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,4CAA4C,CAAC,CAAC;QAE7E,QAAQ,EAAE,CAAC;QAEX,oEAAoE;QACpE,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC9B,QAAQ,EAAE,CAAC;IACb,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|