lula2 0.6.6-nightly.0 → 0.7.0
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/README.md +1 -0
- package/dist/_app/immutable/assets/0.KSamNhnP.css +1 -0
- package/dist/_app/immutable/chunks/24UDoAn4.js +65 -0
- package/dist/_app/immutable/chunks/B0ygo1VA.js +2 -0
- package/dist/_app/immutable/chunks/B2nEDjq4.js +1 -0
- package/dist/_app/immutable/chunks/BNHHvtTX.js +1 -0
- package/dist/_app/immutable/chunks/CpBmCwmc.js +1 -0
- package/dist/_app/immutable/chunks/CttDkklr.js +2 -0
- package/dist/_app/immutable/chunks/P3psI8RV.js +1 -0
- package/dist/_app/immutable/chunks/iLqChAUt.js +1 -0
- package/dist/_app/immutable/chunks/iYE0hyoB.js +1 -0
- package/dist/_app/immutable/chunks/l0xMBaDV.js +1 -0
- package/dist/_app/immutable/entry/app.DK674slU.js +2 -0
- package/dist/_app/immutable/entry/start.BzhV34ug.js +1 -0
- package/dist/_app/immutable/nodes/0.CX00wLgP.js +2 -0
- package/dist/_app/immutable/nodes/1.eWlwSy7C.js +1 -0
- package/dist/_app/immutable/nodes/2.C8hXOpRf.js +1 -0
- package/dist/_app/immutable/nodes/3.DeLiyve3.js +1 -0
- package/dist/_app/immutable/nodes/{4.CjJtAzwd.js → 4.CSVxIlBM.js} +1 -1
- package/dist/_app/version.json +1 -1
- package/dist/cli/commands/ui.js +281 -14
- package/dist/cli/server/index.js +281 -14
- package/dist/cli/server/server.js +281 -14
- package/dist/cli/server/serverState.js +247 -3
- package/dist/cli/server/spreadsheetRoutes.js +821 -2
- package/dist/cli/server/websocketServer.js +281 -14
- package/dist/index.html +11 -11
- package/dist/index.js +281 -14
- package/package.json +21 -20
- package/src/lib/components/git-status/GitStatusDropdown.svelte +261 -0
- package/src/lib/components/git-status/index.ts +4 -0
- package/src/lib/types.ts +19 -0
- package/src/routes/+layout.svelte +4 -0
- package/dist/_app/immutable/assets/0.DT0yw00X.css +0 -1
- package/dist/_app/immutable/chunks/BAMA-SMn.js +0 -1
- package/dist/_app/immutable/chunks/BIpNkEdo.js +0 -65
- package/dist/_app/immutable/chunks/BOeu7SQt.js +0 -2
- package/dist/_app/immutable/chunks/Bvx51L6t.js +0 -1
- package/dist/_app/immutable/chunks/DQTRhwGS.js +0 -1
- package/dist/_app/immutable/chunks/DkIUt-Ae.js +0 -1
- package/dist/_app/immutable/chunks/DoNUPQ2F.js +0 -1
- package/dist/_app/immutable/chunks/DsnmJJEf.js +0 -1
- package/dist/_app/immutable/chunks/kqS9jm6m.js +0 -2
- package/dist/_app/immutable/chunks/oPg1Ic49.js +0 -1
- package/dist/_app/immutable/entry/app.Dqwn7sww.js +0 -2
- package/dist/_app/immutable/entry/start.B-O5NM7y.js +0 -1
- package/dist/_app/immutable/nodes/0.CZ3i370e.js +0 -2
- package/dist/_app/immutable/nodes/1.C9kqHM-h.js +0 -1
- package/dist/_app/immutable/nodes/2.BDP2l8IW.js +0 -1
- package/dist/_app/immutable/nodes/3.HVBwzXF9.js +0 -1
|
@@ -7,7 +7,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
7
7
|
import { glob } from "glob";
|
|
8
8
|
import * as yaml4 from "js-yaml";
|
|
9
9
|
import multer from "multer";
|
|
10
|
-
import { dirname, join as join2, relative } from "path";
|
|
10
|
+
import { dirname, join as join2, relative as relative2 } from "path";
|
|
11
11
|
|
|
12
12
|
// cli/utils/debug.ts
|
|
13
13
|
var debugEnabled = false;
|
|
@@ -27,10 +27,803 @@ import * as yaml2 from "js-yaml";
|
|
|
27
27
|
import * as yaml from "js-yaml";
|
|
28
28
|
|
|
29
29
|
// cli/server/infrastructure/gitHistory.ts
|
|
30
|
+
import * as fs from "fs";
|
|
30
31
|
import * as git from "isomorphic-git";
|
|
32
|
+
import { relative } from "path";
|
|
33
|
+
import { execSync } from "child_process";
|
|
31
34
|
|
|
32
35
|
// cli/server/infrastructure/yamlDiff.ts
|
|
33
36
|
import * as yaml3 from "js-yaml";
|
|
37
|
+
function createYamlDiff(oldYaml, newYaml, isArrayFile = false) {
|
|
38
|
+
try {
|
|
39
|
+
const emptyDefault = isArrayFile ? "[]" : "{}";
|
|
40
|
+
const oldData = yaml3.load(oldYaml || emptyDefault);
|
|
41
|
+
const newData = yaml3.load(newYaml || emptyDefault);
|
|
42
|
+
const changes = compareValues(oldData, newData, "");
|
|
43
|
+
return {
|
|
44
|
+
hasChanges: changes.length > 0,
|
|
45
|
+
changes,
|
|
46
|
+
summary: generateSummary(changes)
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error parsing YAML for diff:", error);
|
|
50
|
+
return {
|
|
51
|
+
hasChanges: false,
|
|
52
|
+
changes: [],
|
|
53
|
+
summary: "Error parsing YAML content"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function compareValues(oldValue, newValue, basePath) {
|
|
58
|
+
const changes = [];
|
|
59
|
+
if (oldValue === null || oldValue === void 0) {
|
|
60
|
+
if (newValue !== null && newValue !== void 0) {
|
|
61
|
+
changes.push({
|
|
62
|
+
type: "added",
|
|
63
|
+
path: basePath || "root",
|
|
64
|
+
newValue,
|
|
65
|
+
description: `Added ${basePath || "content"}`
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return changes;
|
|
69
|
+
}
|
|
70
|
+
if (newValue === null || newValue === void 0) {
|
|
71
|
+
changes.push({
|
|
72
|
+
type: "removed",
|
|
73
|
+
path: basePath || "root",
|
|
74
|
+
oldValue,
|
|
75
|
+
description: `Removed ${basePath || "content"}`
|
|
76
|
+
});
|
|
77
|
+
return changes;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(oldValue) || Array.isArray(newValue)) {
|
|
80
|
+
return compareArrays(oldValue, newValue, basePath);
|
|
81
|
+
}
|
|
82
|
+
if (typeof oldValue === "object" && typeof newValue === "object") {
|
|
83
|
+
return compareObjects(oldValue, newValue, basePath);
|
|
84
|
+
}
|
|
85
|
+
if (oldValue !== newValue) {
|
|
86
|
+
changes.push({
|
|
87
|
+
type: "modified",
|
|
88
|
+
path: basePath || "root",
|
|
89
|
+
oldValue,
|
|
90
|
+
newValue,
|
|
91
|
+
description: `Changed ${basePath || "value"}`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return changes;
|
|
95
|
+
}
|
|
96
|
+
function compareObjects(oldObj, newObj, basePath) {
|
|
97
|
+
const changes = [];
|
|
98
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
99
|
+
for (const key of allKeys) {
|
|
100
|
+
const currentPath = basePath ? `${basePath}.${key}` : key;
|
|
101
|
+
const oldValue = oldObj[key];
|
|
102
|
+
const newValue = newObj[key];
|
|
103
|
+
if (!(key in oldObj)) {
|
|
104
|
+
changes.push({
|
|
105
|
+
type: "added",
|
|
106
|
+
path: currentPath,
|
|
107
|
+
newValue,
|
|
108
|
+
description: `Added ${key}`
|
|
109
|
+
});
|
|
110
|
+
} else if (!(key in newObj)) {
|
|
111
|
+
changes.push({
|
|
112
|
+
type: "removed",
|
|
113
|
+
path: currentPath,
|
|
114
|
+
oldValue,
|
|
115
|
+
description: `Removed ${key}`
|
|
116
|
+
});
|
|
117
|
+
} else if (!deepEqual(oldValue, newValue)) {
|
|
118
|
+
if (typeof oldValue === "object" && typeof newValue === "object") {
|
|
119
|
+
changes.push(...compareValues(oldValue, newValue, currentPath));
|
|
120
|
+
} else {
|
|
121
|
+
changes.push({
|
|
122
|
+
type: "modified",
|
|
123
|
+
path: currentPath,
|
|
124
|
+
oldValue,
|
|
125
|
+
newValue,
|
|
126
|
+
description: `Changed ${key}`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return changes;
|
|
132
|
+
}
|
|
133
|
+
function compareArrays(oldArr, newArr, basePath) {
|
|
134
|
+
const changes = [];
|
|
135
|
+
if (!Array.isArray(oldArr) && Array.isArray(newArr)) {
|
|
136
|
+
changes.push({
|
|
137
|
+
type: "modified",
|
|
138
|
+
path: basePath || "root",
|
|
139
|
+
oldValue: oldArr,
|
|
140
|
+
newValue: newArr,
|
|
141
|
+
description: `Changed ${basePath || "value"} from non-array to array`
|
|
142
|
+
});
|
|
143
|
+
return changes;
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(oldArr) && !Array.isArray(newArr)) {
|
|
146
|
+
changes.push({
|
|
147
|
+
type: "modified",
|
|
148
|
+
path: basePath || "root",
|
|
149
|
+
oldValue: oldArr,
|
|
150
|
+
newValue: newArr,
|
|
151
|
+
description: `Changed ${basePath || "value"} from array to non-array`
|
|
152
|
+
});
|
|
153
|
+
return changes;
|
|
154
|
+
}
|
|
155
|
+
const oldArray = oldArr;
|
|
156
|
+
const newArray = newArr;
|
|
157
|
+
if (isMappingArray(oldArray) || isMappingArray(newArray)) {
|
|
158
|
+
return compareMappingArrays(oldArray, newArray, basePath);
|
|
159
|
+
}
|
|
160
|
+
if (oldArray.length !== newArray.length) {
|
|
161
|
+
changes.push({
|
|
162
|
+
type: "modified",
|
|
163
|
+
path: basePath || "root",
|
|
164
|
+
oldValue: oldArray,
|
|
165
|
+
newValue: newArray,
|
|
166
|
+
description: `Array ${basePath || "items"} changed from ${oldArray.length} to ${newArray.length} items`
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
for (let i = 0; i < oldArray.length; i++) {
|
|
170
|
+
const elementPath = `${basePath}[${i}]`;
|
|
171
|
+
if (!deepEqual(oldArray[i], newArray[i])) {
|
|
172
|
+
changes.push(...compareValues(oldArray[i], newArray[i], elementPath));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return changes;
|
|
177
|
+
}
|
|
178
|
+
function isMappingArray(arr) {
|
|
179
|
+
if (!Array.isArray(arr) || arr.length === 0) return false;
|
|
180
|
+
const firstItem = arr[0];
|
|
181
|
+
return typeof firstItem === "object" && firstItem !== null && "control_id" in firstItem && "uuid" in firstItem;
|
|
182
|
+
}
|
|
183
|
+
function compareMappingArrays(oldArr, newArr, basePath) {
|
|
184
|
+
const changes = [];
|
|
185
|
+
const oldMappings = /* @__PURE__ */ new Map();
|
|
186
|
+
const newMappings = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const item of oldArr) {
|
|
188
|
+
if (typeof item === "object" && item !== null && "uuid" in item) {
|
|
189
|
+
oldMappings.set(item.uuid, item);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const item of newArr) {
|
|
193
|
+
if (typeof item === "object" && item !== null && "uuid" in item) {
|
|
194
|
+
newMappings.set(item.uuid, item);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [uuid, mapping] of newMappings) {
|
|
198
|
+
if (!oldMappings.has(uuid)) {
|
|
199
|
+
changes.push({
|
|
200
|
+
type: "added",
|
|
201
|
+
path: `mapping`,
|
|
202
|
+
newValue: mapping,
|
|
203
|
+
description: `Added mapping`
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (const [uuid, mapping] of oldMappings) {
|
|
208
|
+
if (!newMappings.has(uuid)) {
|
|
209
|
+
changes.push({
|
|
210
|
+
type: "removed",
|
|
211
|
+
path: `mapping`,
|
|
212
|
+
oldValue: mapping,
|
|
213
|
+
description: `Removed mapping`
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const [uuid, oldMapping] of oldMappings) {
|
|
218
|
+
if (newMappings.has(uuid)) {
|
|
219
|
+
const newMapping = newMappings.get(uuid);
|
|
220
|
+
if (!deepEqual(oldMapping, newMapping)) {
|
|
221
|
+
changes.push({
|
|
222
|
+
type: "modified",
|
|
223
|
+
path: `mapping`,
|
|
224
|
+
oldValue: oldMapping,
|
|
225
|
+
newValue: newMapping,
|
|
226
|
+
description: `Modified mapping`
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (changes.length === 0 && oldArr.length !== newArr.length) {
|
|
232
|
+
changes.push({
|
|
233
|
+
type: "modified",
|
|
234
|
+
path: basePath || "mappings",
|
|
235
|
+
oldValue: oldArr,
|
|
236
|
+
newValue: newArr,
|
|
237
|
+
description: `Mappings changed from ${oldArr.length} to ${newArr.length} items`
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return changes;
|
|
241
|
+
}
|
|
242
|
+
function deepEqual(a, b) {
|
|
243
|
+
if (a === b) return true;
|
|
244
|
+
if (a === null || b === null) return false;
|
|
245
|
+
if (typeof a !== typeof b) return false;
|
|
246
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
247
|
+
if (a.length !== b.length) return false;
|
|
248
|
+
for (let i = 0; i < a.length; i++) {
|
|
249
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
254
|
+
const aObj = a;
|
|
255
|
+
const bObj = b;
|
|
256
|
+
const aKeys = Object.keys(aObj);
|
|
257
|
+
const bKeys = Object.keys(bObj);
|
|
258
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
259
|
+
for (const key of aKeys) {
|
|
260
|
+
if (!bKeys.includes(key)) return false;
|
|
261
|
+
if (!deepEqual(aObj[key], bObj[key])) return false;
|
|
262
|
+
}
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
function generateSummary(changes) {
|
|
268
|
+
if (changes.length === 0) {
|
|
269
|
+
return "No changes detected";
|
|
270
|
+
}
|
|
271
|
+
const added = changes.filter((c) => c.type === "added").length;
|
|
272
|
+
const removed = changes.filter((c) => c.type === "removed").length;
|
|
273
|
+
const modified = changes.filter((c) => c.type === "modified").length;
|
|
274
|
+
const parts = [];
|
|
275
|
+
if (added > 0) parts.push(`${added} added`);
|
|
276
|
+
if (removed > 0) parts.push(`${removed} removed`);
|
|
277
|
+
if (modified > 0) parts.push(`${modified} modified`);
|
|
278
|
+
return parts.join(", ");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// cli/server/infrastructure/gitHistory.ts
|
|
282
|
+
var GitHistoryUtil = class {
|
|
283
|
+
baseDir;
|
|
284
|
+
execSync;
|
|
285
|
+
constructor(baseDir, execSyncFn) {
|
|
286
|
+
this.baseDir = baseDir;
|
|
287
|
+
this.execSync = execSyncFn || execSync;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Execute a git command using native git binary with credentials support
|
|
291
|
+
*/
|
|
292
|
+
async executeGitCommand(command, cwd) {
|
|
293
|
+
try {
|
|
294
|
+
const workingDir = cwd || await git.findRoot({ fs, filepath: process.cwd() });
|
|
295
|
+
const output = this.execSync(command, {
|
|
296
|
+
cwd: workingDir,
|
|
297
|
+
encoding: "utf8",
|
|
298
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
299
|
+
});
|
|
300
|
+
return { success: true, output: output.toString() };
|
|
301
|
+
} catch (error) {
|
|
302
|
+
return {
|
|
303
|
+
success: false,
|
|
304
|
+
output: "",
|
|
305
|
+
error: error.stderr?.toString() || error.message
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Check if the directory is a git repository
|
|
311
|
+
*/
|
|
312
|
+
async isGitRepository() {
|
|
313
|
+
try {
|
|
314
|
+
const gitDir = await git.findRoot({ fs, filepath: process.cwd() });
|
|
315
|
+
return !!gitDir;
|
|
316
|
+
} catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Get git history for a specific file
|
|
322
|
+
*/
|
|
323
|
+
async getFileHistory(filePath, limit = 50) {
|
|
324
|
+
const isGitRepo = await this.isGitRepository();
|
|
325
|
+
if (!isGitRepo) {
|
|
326
|
+
return {
|
|
327
|
+
filePath,
|
|
328
|
+
commits: [],
|
|
329
|
+
totalCommits: 0,
|
|
330
|
+
firstCommit: null,
|
|
331
|
+
lastCommit: null
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
336
|
+
const relativePath = relative(gitRoot, filePath);
|
|
337
|
+
const commits = await git.log({
|
|
338
|
+
fs,
|
|
339
|
+
dir: gitRoot,
|
|
340
|
+
filepath: relativePath,
|
|
341
|
+
depth: limit
|
|
342
|
+
});
|
|
343
|
+
if (!commits || commits.length === 0) {
|
|
344
|
+
return {
|
|
345
|
+
filePath,
|
|
346
|
+
commits: [],
|
|
347
|
+
totalCommits: 0,
|
|
348
|
+
firstCommit: null,
|
|
349
|
+
lastCommit: null
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const gitCommits = await this.convertIsomorphicCommits(commits, relativePath, gitRoot);
|
|
353
|
+
return {
|
|
354
|
+
filePath,
|
|
355
|
+
commits: gitCommits,
|
|
356
|
+
totalCommits: gitCommits.length,
|
|
357
|
+
firstCommit: gitCommits[gitCommits.length - 1] || null,
|
|
358
|
+
lastCommit: gitCommits[0] || null
|
|
359
|
+
};
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const err = error;
|
|
362
|
+
if (err?.code === "NotFoundError" || err?.message?.includes("Could not find file")) {
|
|
363
|
+
return {
|
|
364
|
+
filePath,
|
|
365
|
+
commits: [],
|
|
366
|
+
totalCommits: 0,
|
|
367
|
+
firstCommit: null,
|
|
368
|
+
lastCommit: null
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
console.error(`Unexpected error getting git history for ${filePath}:`, error);
|
|
372
|
+
return {
|
|
373
|
+
filePath,
|
|
374
|
+
commits: [],
|
|
375
|
+
totalCommits: 0,
|
|
376
|
+
firstCommit: null,
|
|
377
|
+
lastCommit: null
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get the total number of commits for a file
|
|
383
|
+
*/
|
|
384
|
+
async getFileCommitCount(filePath) {
|
|
385
|
+
const isGitRepo = await this.isGitRepository();
|
|
386
|
+
if (!isGitRepo) {
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
391
|
+
const relativePath = relative(gitRoot, filePath);
|
|
392
|
+
const commits = await git.log({
|
|
393
|
+
fs,
|
|
394
|
+
dir: gitRoot,
|
|
395
|
+
filepath: relativePath
|
|
396
|
+
});
|
|
397
|
+
return commits.length;
|
|
398
|
+
} catch {
|
|
399
|
+
return 0;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Get the latest commit info for a file
|
|
404
|
+
*/
|
|
405
|
+
async getLatestCommit(filePath) {
|
|
406
|
+
const history = await this.getFileHistory(filePath, 1);
|
|
407
|
+
return history.lastCommit;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get file content at a specific commit (public method)
|
|
411
|
+
*/
|
|
412
|
+
async getFileContentAtCommit(filePath, commitHash) {
|
|
413
|
+
const isGitRepo = await this.isGitRepository();
|
|
414
|
+
if (!isGitRepo) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
419
|
+
const relativePath = relative(gitRoot, filePath);
|
|
420
|
+
return await this.getFileAtCommit(commitHash, relativePath, gitRoot);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.error(`Error getting file content at commit ${commitHash}:`, error);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Convert isomorphic-git commits to our GitCommit format
|
|
428
|
+
*/
|
|
429
|
+
async convertIsomorphicCommits(commits, relativePath, gitRoot) {
|
|
430
|
+
const gitCommits = [];
|
|
431
|
+
for (let i = 0; i < commits.length; i++) {
|
|
432
|
+
const commit = commits[i];
|
|
433
|
+
const includeDiff = i < 5;
|
|
434
|
+
let changes = { insertions: 0, deletions: 0, files: 1 };
|
|
435
|
+
let diff;
|
|
436
|
+
let yamlDiff;
|
|
437
|
+
if (includeDiff) {
|
|
438
|
+
try {
|
|
439
|
+
const diffResult = await this.getCommitDiff(commit.oid, relativePath, gitRoot);
|
|
440
|
+
changes = diffResult.changes;
|
|
441
|
+
diff = diffResult.diff;
|
|
442
|
+
yamlDiff = diffResult.yamlDiff;
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
gitCommits.push({
|
|
447
|
+
hash: commit.oid,
|
|
448
|
+
shortHash: commit.oid.substring(0, 7),
|
|
449
|
+
author: commit.commit.author.name,
|
|
450
|
+
authorEmail: commit.commit.author.email,
|
|
451
|
+
date: new Date(commit.commit.author.timestamp * 1e3).toISOString(),
|
|
452
|
+
message: commit.commit.message,
|
|
453
|
+
changes,
|
|
454
|
+
...diff && { diff },
|
|
455
|
+
...yamlDiff && { yamlDiff }
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
return gitCommits;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Get diff for a specific commit and file
|
|
462
|
+
*/
|
|
463
|
+
async getCommitDiff(commitOid, relativePath, gitRoot) {
|
|
464
|
+
try {
|
|
465
|
+
const commit = await git.readCommit({ fs, dir: gitRoot, oid: commitOid });
|
|
466
|
+
const parentOid = commit.commit.parent.length > 0 ? commit.commit.parent[0] : null;
|
|
467
|
+
if (!parentOid) {
|
|
468
|
+
const currentContent2 = await this.getFileAtCommit(commitOid, relativePath, gitRoot);
|
|
469
|
+
if (currentContent2) {
|
|
470
|
+
const lines = currentContent2.split("\n");
|
|
471
|
+
const isMappingFile2 = relativePath.includes("-mappings.yaml");
|
|
472
|
+
const yamlDiff2 = createYamlDiff("", currentContent2, isMappingFile2);
|
|
473
|
+
return {
|
|
474
|
+
changes: { insertions: lines.length, deletions: 0, files: 1 },
|
|
475
|
+
diff: `--- /dev/null
|
|
476
|
+
+++ b/${relativePath}
|
|
477
|
+
@@ -0,0 +1,${lines.length} @@
|
|
478
|
+
` + lines.map((line) => "+" + line).join("\n"),
|
|
479
|
+
yamlDiff: yamlDiff2
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return { changes: { insertions: 0, deletions: 0, files: 1 } };
|
|
483
|
+
}
|
|
484
|
+
const currentContent = await this.getFileAtCommit(commitOid, relativePath, gitRoot);
|
|
485
|
+
const parentContent = await this.getFileAtCommit(parentOid, relativePath, gitRoot);
|
|
486
|
+
if (!currentContent && !parentContent) {
|
|
487
|
+
return { changes: { insertions: 0, deletions: 0, files: 1 } };
|
|
488
|
+
}
|
|
489
|
+
const currentLines = currentContent ? currentContent.split("\n") : [];
|
|
490
|
+
const parentLines = parentContent ? parentContent.split("\n") : [];
|
|
491
|
+
const diff = await this.createSimpleDiff(parentLines, currentLines, relativePath);
|
|
492
|
+
const { insertions, deletions } = this.countChanges(parentLines, currentLines);
|
|
493
|
+
const isMappingFile = relativePath.includes("-mappings.yaml");
|
|
494
|
+
const yamlDiff = createYamlDiff(parentContent || "", currentContent || "", isMappingFile);
|
|
495
|
+
return {
|
|
496
|
+
changes: { insertions, deletions, files: 1 },
|
|
497
|
+
diff,
|
|
498
|
+
yamlDiff
|
|
499
|
+
};
|
|
500
|
+
} catch {
|
|
501
|
+
return { changes: { insertions: 0, deletions: 0, files: 1 } };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Get file content at a specific commit
|
|
506
|
+
*/
|
|
507
|
+
async getFileAtCommit(commitOid, filepath, gitRoot) {
|
|
508
|
+
try {
|
|
509
|
+
const { blob } = await git.readBlob({
|
|
510
|
+
fs,
|
|
511
|
+
dir: gitRoot,
|
|
512
|
+
oid: commitOid,
|
|
513
|
+
filepath
|
|
514
|
+
});
|
|
515
|
+
return new TextDecoder().decode(blob);
|
|
516
|
+
} catch (_error) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Create a simple unified diff between two file versions
|
|
522
|
+
*/
|
|
523
|
+
async createSimpleDiff(oldLines, newLines, filepath) {
|
|
524
|
+
const diffLines = [];
|
|
525
|
+
diffLines.push(`--- a/${filepath}`);
|
|
526
|
+
diffLines.push(`+++ b/${filepath}`);
|
|
527
|
+
const oldCount = oldLines.length;
|
|
528
|
+
const newCount = newLines.length;
|
|
529
|
+
diffLines.push(`@@ -1,${oldCount} +1,${newCount} @@`);
|
|
530
|
+
let i = 0, j = 0;
|
|
531
|
+
while (i < oldLines.length || j < newLines.length) {
|
|
532
|
+
if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {
|
|
533
|
+
diffLines.push(` ${oldLines[i]}`);
|
|
534
|
+
i++;
|
|
535
|
+
j++;
|
|
536
|
+
} else if (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {
|
|
537
|
+
diffLines.push(`-${oldLines[i]}`);
|
|
538
|
+
i++;
|
|
539
|
+
} else if (j < newLines.length) {
|
|
540
|
+
diffLines.push(`+${newLines[j]}`);
|
|
541
|
+
j++;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return diffLines.join("\n");
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Count insertions and deletions between two file versions
|
|
548
|
+
*/
|
|
549
|
+
countChanges(oldLines, newLines) {
|
|
550
|
+
let insertions = 0;
|
|
551
|
+
let deletions = 0;
|
|
552
|
+
const maxLines = Math.max(oldLines.length, newLines.length);
|
|
553
|
+
for (let i = 0; i < maxLines; i++) {
|
|
554
|
+
const oldLine = i < oldLines.length ? oldLines[i] : null;
|
|
555
|
+
const newLine = i < newLines.length ? newLines[i] : null;
|
|
556
|
+
if (oldLine === null) {
|
|
557
|
+
insertions++;
|
|
558
|
+
} else if (newLine === null) {
|
|
559
|
+
deletions++;
|
|
560
|
+
} else if (oldLine !== newLine) {
|
|
561
|
+
insertions++;
|
|
562
|
+
deletions++;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return { insertions, deletions };
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get git stats for the entire repository
|
|
569
|
+
*/
|
|
570
|
+
async getRepositoryStats() {
|
|
571
|
+
const isGitRepo = await this.isGitRepository();
|
|
572
|
+
if (!isGitRepo) {
|
|
573
|
+
return {
|
|
574
|
+
totalCommits: 0,
|
|
575
|
+
contributors: 0,
|
|
576
|
+
lastCommitDate: null,
|
|
577
|
+
firstCommitDate: null
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
582
|
+
const commits = await git.log({ fs, dir: gitRoot });
|
|
583
|
+
const contributorEmails = /* @__PURE__ */ new Set();
|
|
584
|
+
commits.forEach((commit) => {
|
|
585
|
+
contributorEmails.add(commit.commit.author.email);
|
|
586
|
+
});
|
|
587
|
+
const firstCommit = commits[commits.length - 1];
|
|
588
|
+
const lastCommit = commits[0];
|
|
589
|
+
return {
|
|
590
|
+
totalCommits: commits.length,
|
|
591
|
+
contributors: contributorEmails.size,
|
|
592
|
+
lastCommitDate: lastCommit ? new Date(lastCommit.commit.author.timestamp * 1e3).toISOString() : null,
|
|
593
|
+
firstCommitDate: firstCommit ? new Date(firstCommit.commit.author.timestamp * 1e3).toISOString() : null
|
|
594
|
+
};
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error("Error getting repository stats:", error);
|
|
597
|
+
return {
|
|
598
|
+
totalCommits: 0,
|
|
599
|
+
contributors: 0,
|
|
600
|
+
lastCommitDate: null,
|
|
601
|
+
firstCommitDate: null
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get current branch name
|
|
607
|
+
*/
|
|
608
|
+
async getCurrentBranch() {
|
|
609
|
+
try {
|
|
610
|
+
const isGitRepo = await this.isGitRepository();
|
|
611
|
+
if (!isGitRepo) {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
615
|
+
const branch = await git.currentBranch({ fs, dir: gitRoot });
|
|
616
|
+
return branch || null;
|
|
617
|
+
} catch (error) {
|
|
618
|
+
console.error("Error getting current branch:", error);
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Get git status information
|
|
624
|
+
*/
|
|
625
|
+
async getGitStatus() {
|
|
626
|
+
try {
|
|
627
|
+
const isGitRepo = await this.isGitRepository();
|
|
628
|
+
if (!isGitRepo) {
|
|
629
|
+
return {
|
|
630
|
+
isGitRepository: false,
|
|
631
|
+
currentBranch: null,
|
|
632
|
+
branchInfo: null,
|
|
633
|
+
canPull: false,
|
|
634
|
+
canPush: false
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
const currentBranch2 = await this.getCurrentBranch();
|
|
638
|
+
if (!currentBranch2) {
|
|
639
|
+
return {
|
|
640
|
+
isGitRepository: true,
|
|
641
|
+
currentBranch: null,
|
|
642
|
+
branchInfo: null,
|
|
643
|
+
canPull: false,
|
|
644
|
+
canPush: false
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const branchInfo = await this.getBranchInfo(currentBranch2);
|
|
648
|
+
return {
|
|
649
|
+
isGitRepository: true,
|
|
650
|
+
currentBranch: currentBranch2,
|
|
651
|
+
branchInfo,
|
|
652
|
+
canPull: branchInfo?.isBehind || false,
|
|
653
|
+
canPush: branchInfo?.isAhead || false
|
|
654
|
+
};
|
|
655
|
+
} catch (error) {
|
|
656
|
+
console.error("Error getting git status:", error);
|
|
657
|
+
return {
|
|
658
|
+
isGitRepository: false,
|
|
659
|
+
currentBranch: null,
|
|
660
|
+
branchInfo: null,
|
|
661
|
+
canPull: false,
|
|
662
|
+
canPush: false
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Get branch comparison information
|
|
668
|
+
*/
|
|
669
|
+
async getBranchInfo(branchName) {
|
|
670
|
+
try {
|
|
671
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
672
|
+
const localCommits = await git.log({ fs, dir: gitRoot, ref: branchName });
|
|
673
|
+
try {
|
|
674
|
+
const remotes = await git.listRemotes({ fs, dir: gitRoot });
|
|
675
|
+
for (const remote of remotes) {
|
|
676
|
+
try {
|
|
677
|
+
const fetchResult = await this.executeGitCommand(`git fetch ${remote.remote}`, gitRoot);
|
|
678
|
+
if (!fetchResult.success) {
|
|
679
|
+
console.warn(`Could not fetch from remote ${remote.remote}:`, fetchResult.error);
|
|
680
|
+
}
|
|
681
|
+
} catch (fetchError) {
|
|
682
|
+
console.warn(`Could not fetch from remote ${remote.remote}:`, fetchError);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
let remoteCommits = [];
|
|
686
|
+
let foundRemote = false;
|
|
687
|
+
for (const remote of remotes) {
|
|
688
|
+
const remoteBranchRef = `${remote.remote}/${branchName}`;
|
|
689
|
+
try {
|
|
690
|
+
remoteCommits = await git.log({ fs, dir: gitRoot, ref: remoteBranchRef });
|
|
691
|
+
foundRemote = true;
|
|
692
|
+
break;
|
|
693
|
+
} catch (error) {
|
|
694
|
+
console.warn(`Could not get commits for ${remoteBranchRef}: ${error}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!foundRemote || remoteCommits.length === 0) {
|
|
698
|
+
const lastCommit2 = localCommits[0];
|
|
699
|
+
return {
|
|
700
|
+
currentBranch: branchName,
|
|
701
|
+
isAhead: false,
|
|
702
|
+
isBehind: false,
|
|
703
|
+
aheadCount: 0,
|
|
704
|
+
behindCount: 0,
|
|
705
|
+
lastCommitDate: lastCommit2 ? new Date(lastCommit2.commit.author.timestamp * 1e3).toISOString() : null,
|
|
706
|
+
lastCommitMessage: lastCommit2?.commit.message || null,
|
|
707
|
+
hasUnpushedChanges: false
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
const localHashes = new Set(localCommits.map((c) => c.oid));
|
|
711
|
+
const remoteHashes = new Set(remoteCommits.map((c) => c.oid));
|
|
712
|
+
const aheadCount = localCommits.filter((c) => !remoteHashes.has(c.oid)).length;
|
|
713
|
+
const behindCount = remoteCommits.filter((c) => !localHashes.has(c.oid)).length;
|
|
714
|
+
const lastCommit = localCommits[0];
|
|
715
|
+
return {
|
|
716
|
+
currentBranch: branchName,
|
|
717
|
+
isAhead: aheadCount > 0,
|
|
718
|
+
isBehind: behindCount > 0,
|
|
719
|
+
aheadCount,
|
|
720
|
+
behindCount,
|
|
721
|
+
lastCommitDate: lastCommit ? new Date(lastCommit.commit.author.timestamp * 1e3).toISOString() : null,
|
|
722
|
+
lastCommitMessage: lastCommit?.commit.message || null,
|
|
723
|
+
hasUnpushedChanges: aheadCount > 0
|
|
724
|
+
};
|
|
725
|
+
} catch {
|
|
726
|
+
const lastCommit = localCommits[0];
|
|
727
|
+
return {
|
|
728
|
+
currentBranch: branchName,
|
|
729
|
+
isAhead: false,
|
|
730
|
+
isBehind: false,
|
|
731
|
+
aheadCount: 0,
|
|
732
|
+
behindCount: 0,
|
|
733
|
+
lastCommitDate: lastCommit ? new Date(lastCommit.commit.author.timestamp * 1e3).toISOString() : null,
|
|
734
|
+
lastCommitMessage: lastCommit?.commit.message || null,
|
|
735
|
+
hasUnpushedChanges: false
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
} catch (error) {
|
|
739
|
+
console.error("Error getting branch info:", error);
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Fetch updates from remote repositories using native git command
|
|
745
|
+
*/
|
|
746
|
+
async fetchFromRemotes() {
|
|
747
|
+
try {
|
|
748
|
+
const isGitRepo = await this.isGitRepository();
|
|
749
|
+
if (!isGitRepo) {
|
|
750
|
+
return { success: false, message: "Not a git repository", details: [] };
|
|
751
|
+
}
|
|
752
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
753
|
+
const remotes = await git.listRemotes({ fs, dir: gitRoot });
|
|
754
|
+
if (remotes.length === 0) {
|
|
755
|
+
return { success: true, message: "No remotes configured", details: [] };
|
|
756
|
+
}
|
|
757
|
+
const details = [];
|
|
758
|
+
let hasErrors = false;
|
|
759
|
+
for (const remote of remotes) {
|
|
760
|
+
try {
|
|
761
|
+
const fetchResult = await this.executeGitCommand(`git fetch ${remote.remote}`, gitRoot);
|
|
762
|
+
if (fetchResult.success) {
|
|
763
|
+
details.push(`Fetched from ${remote.remote}`);
|
|
764
|
+
} else {
|
|
765
|
+
details.push(`Failed to fetch from ${remote.remote}: ${fetchResult.error}`);
|
|
766
|
+
hasErrors = true;
|
|
767
|
+
}
|
|
768
|
+
} catch (error) {
|
|
769
|
+
details.push(`Error fetching from ${remote.remote}: ${error}`);
|
|
770
|
+
hasErrors = true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
success: !hasErrors,
|
|
775
|
+
message: hasErrors ? "Fetch completed with some errors" : "Successfully fetched from all remotes",
|
|
776
|
+
details
|
|
777
|
+
};
|
|
778
|
+
} catch (error) {
|
|
779
|
+
console.error("Error fetching from remotes:", error);
|
|
780
|
+
return {
|
|
781
|
+
success: false,
|
|
782
|
+
message: error instanceof Error ? error.message : "Unknown error occurred",
|
|
783
|
+
details: []
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Pull changes from remote using native git command
|
|
789
|
+
*/
|
|
790
|
+
async pullChanges() {
|
|
791
|
+
try {
|
|
792
|
+
const isGitRepo = await this.isGitRepository();
|
|
793
|
+
if (!isGitRepo) {
|
|
794
|
+
return { success: false, message: "Not a git repository" };
|
|
795
|
+
}
|
|
796
|
+
const currentBranch2 = await this.getCurrentBranch();
|
|
797
|
+
if (!currentBranch2) {
|
|
798
|
+
return { success: false, message: "No current branch found" };
|
|
799
|
+
}
|
|
800
|
+
const gitRoot = await git.findRoot({ fs, filepath: process.cwd() });
|
|
801
|
+
const remotes = await git.listRemotes({ fs, dir: gitRoot });
|
|
802
|
+
if (remotes.length === 0) {
|
|
803
|
+
return { success: false, message: "No remotes configured" };
|
|
804
|
+
}
|
|
805
|
+
const targetRemote = remotes[0].remote;
|
|
806
|
+
const pullCommand = `git pull ${targetRemote} ${currentBranch2}`;
|
|
807
|
+
const pullResult = await this.executeGitCommand(pullCommand, gitRoot);
|
|
808
|
+
if (!pullResult.success) {
|
|
809
|
+
return {
|
|
810
|
+
success: false,
|
|
811
|
+
message: `Failed to pull changes: ${pullResult.error}`
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
success: true,
|
|
816
|
+
message: pullResult.output || "Successfully pulled changes"
|
|
817
|
+
};
|
|
818
|
+
} catch (error) {
|
|
819
|
+
console.error("Error pulling changes:", error);
|
|
820
|
+
return {
|
|
821
|
+
success: false,
|
|
822
|
+
message: error instanceof Error ? error.message : "Unknown error occurred"
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
34
827
|
|
|
35
828
|
// cli/server/serverState.ts
|
|
36
829
|
var serverState = void 0;
|
|
@@ -66,7 +859,7 @@ async function scanControlSets() {
|
|
|
66
859
|
const controlSets = files.map((file) => {
|
|
67
860
|
const fullPath = join2(baseDir, file);
|
|
68
861
|
const dirPath = dirname(fullPath);
|
|
69
|
-
const relativePath =
|
|
862
|
+
const relativePath = relative2(baseDir, dirPath) || ".";
|
|
70
863
|
try {
|
|
71
864
|
const content = readFileSync(fullPath, "utf8");
|
|
72
865
|
const data = yaml4.load(content);
|
|
@@ -1076,6 +1869,32 @@ router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, re
|
|
|
1076
1869
|
res.status(500).json({ error: "Failed to get sheet previews" });
|
|
1077
1870
|
}
|
|
1078
1871
|
});
|
|
1872
|
+
router.get("/git-status", async (req, res) => {
|
|
1873
|
+
try {
|
|
1874
|
+
const state = getServerState();
|
|
1875
|
+
const gitUtil = new GitHistoryUtil(state.CONTROL_SET_DIR);
|
|
1876
|
+
const gitStatus = await gitUtil.getGitStatus();
|
|
1877
|
+
res.json(gitStatus);
|
|
1878
|
+
} catch (error) {
|
|
1879
|
+
console.error("Error getting git status:", error);
|
|
1880
|
+
res.status(500).json({ error: "Failed to get git status" });
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
router.post("/git-pull", async (req, res) => {
|
|
1884
|
+
try {
|
|
1885
|
+
const state = getServerState();
|
|
1886
|
+
const gitUtil = new GitHistoryUtil(state.CONTROL_SET_DIR);
|
|
1887
|
+
const result = await gitUtil.pullChanges();
|
|
1888
|
+
if (result.success) {
|
|
1889
|
+
res.json(result);
|
|
1890
|
+
} else {
|
|
1891
|
+
res.status(400).json(result);
|
|
1892
|
+
}
|
|
1893
|
+
} catch (error) {
|
|
1894
|
+
console.error("Error pulling changes:", error);
|
|
1895
|
+
res.status(500).json({ error: "Failed to pull changes" });
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1079
1898
|
var spreadsheetRoutes_default = router;
|
|
1080
1899
|
export {
|
|
1081
1900
|
applyNamingConvention,
|