@openspecui/server 2.1.5 → 2.1.7
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/index.mjs +37 -13
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -178,6 +178,14 @@ async function defaultRunGit(cwd, args) {
|
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
+
async function defaultReadPathTimestampMs(absolutePath) {
|
|
182
|
+
try {
|
|
183
|
+
const stats = await stat(absolutePath);
|
|
184
|
+
return Number.isFinite(stats.mtimeMs) && stats.mtimeMs > 0 ? stats.mtimeMs : null;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
181
189
|
function parseShortStat(output) {
|
|
182
190
|
const files = Number(/(\d+)\s+files? changed/.exec(output)?.[1] ?? 0);
|
|
183
191
|
const insertions = Number(/(\d+)\s+insertions?\(\+\)/.exec(output)?.[1] ?? 0);
|
|
@@ -287,17 +295,17 @@ async function resolveDefaultBranch(projectDir, runGit) {
|
|
|
287
295
|
return "main";
|
|
288
296
|
}
|
|
289
297
|
async function collectCommitEntries(options) {
|
|
290
|
-
const { worktreePath, defaultBranch, maxCommitEntries, runGit } = options;
|
|
291
|
-
const
|
|
298
|
+
const { worktreePath, defaultBranch, maxCommitEntries, runGit, readPathTimestampMs } = options;
|
|
299
|
+
const commitEntries = [];
|
|
292
300
|
const commits = await runGit(worktreePath, [
|
|
293
301
|
"log",
|
|
294
|
-
"--format=%H%x1f%s",
|
|
302
|
+
"--format=%H%x1f%ct%x1f%s",
|
|
295
303
|
`-n${maxCommitEntries}`,
|
|
296
304
|
`${defaultBranch}..HEAD`
|
|
297
305
|
]);
|
|
298
306
|
if (commits.ok) for (const line of commits.stdout.split("\n")) {
|
|
299
307
|
if (!line.trim()) continue;
|
|
300
|
-
const [hash, title = ""] = line.split("");
|
|
308
|
+
const [hash, committedAtRaw = "0", title = ""] = line.split("");
|
|
301
309
|
if (!hash) continue;
|
|
302
310
|
const diffResult = await runGit(worktreePath, [
|
|
303
311
|
"show",
|
|
@@ -311,10 +319,12 @@ async function collectCommitEntries(options) {
|
|
|
311
319
|
"--format=",
|
|
312
320
|
hash
|
|
313
321
|
])).stdout.split("\n").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
314
|
-
|
|
322
|
+
const committedAt = Number(committedAtRaw) * 1e3;
|
|
323
|
+
commitEntries.push({
|
|
315
324
|
type: "commit",
|
|
316
325
|
hash,
|
|
317
326
|
title: title.trim() || hash.slice(0, 7),
|
|
327
|
+
committedAt: Number.isFinite(committedAt) && committedAt > 0 ? committedAt : 0,
|
|
318
328
|
relatedChanges: parseRelatedChanges(changedFiles),
|
|
319
329
|
diff: diffResult.ok ? parseNumStat(diffResult.stdout) : EMPTY_DIFF
|
|
320
330
|
});
|
|
@@ -338,20 +348,28 @@ async function collectCommitEntries(options) {
|
|
|
338
348
|
const untrackedFiles = untrackedResult.stdout.split("\n").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
339
349
|
const allUncommittedFiles = new Set([...trackedFiles, ...untrackedFiles]);
|
|
340
350
|
const trackedDiff = trackedResult.ok ? parseNumStat(trackedResult.stdout) : EMPTY_DIFF;
|
|
341
|
-
|
|
351
|
+
const updatedAt = (await Promise.all([...allUncommittedFiles].map((path) => readPathTimestampMs(resolve(worktreePath, path))))).reduce((latest, current) => {
|
|
352
|
+
if (!current || !Number.isFinite(current) || current <= 0) return latest;
|
|
353
|
+
return latest === null || current > latest ? current : latest;
|
|
354
|
+
}, null) ?? null;
|
|
355
|
+
commitEntries.sort((left, right) => {
|
|
356
|
+
if (left.type !== "commit" || right.type !== "commit") return 0;
|
|
357
|
+
return right.committedAt - left.committedAt;
|
|
358
|
+
});
|
|
359
|
+
return [{
|
|
342
360
|
type: "uncommitted",
|
|
343
361
|
title: "Uncommitted",
|
|
362
|
+
updatedAt,
|
|
344
363
|
relatedChanges: parseRelatedChanges([...allUncommittedFiles]),
|
|
345
364
|
diff: {
|
|
346
365
|
files: allUncommittedFiles.size,
|
|
347
366
|
insertions: trackedDiff.insertions,
|
|
348
367
|
deletions: trackedDiff.deletions
|
|
349
368
|
}
|
|
350
|
-
}
|
|
351
|
-
return entries;
|
|
369
|
+
}, ...commitEntries];
|
|
352
370
|
}
|
|
353
371
|
async function collectWorktree(options) {
|
|
354
|
-
const { projectDir, worktree, defaultBranch, runGit, maxCommitEntries } = options;
|
|
372
|
+
const { projectDir, worktree, defaultBranch, runGit, maxCommitEntries, readPathTimestampMs } = options;
|
|
355
373
|
const worktreePath = resolve(worktree.path);
|
|
356
374
|
const resolvedProjectDir = resolve(projectDir);
|
|
357
375
|
const aheadBehindResult = await runGit(worktreePath, [
|
|
@@ -377,7 +395,8 @@ async function collectWorktree(options) {
|
|
|
377
395
|
worktreePath,
|
|
378
396
|
defaultBranch,
|
|
379
397
|
maxCommitEntries,
|
|
380
|
-
runGit
|
|
398
|
+
runGit,
|
|
399
|
+
readPathTimestampMs
|
|
381
400
|
});
|
|
382
401
|
return {
|
|
383
402
|
path: worktreePath,
|
|
@@ -415,6 +434,7 @@ async function removeDetachedDashboardGitWorktree(options) {
|
|
|
415
434
|
async function buildDashboardGitSnapshot(options) {
|
|
416
435
|
const runGit = options.runGit ?? defaultRunGit;
|
|
417
436
|
const maxCommitEntries = options.maxCommitEntries ?? 8;
|
|
437
|
+
const readPathTimestampMs = options.readPathTimestampMs ?? defaultReadPathTimestampMs;
|
|
418
438
|
const resolvedProjectDir = resolve(options.projectDir);
|
|
419
439
|
const defaultBranch = await resolveDefaultBranch(resolvedProjectDir, runGit);
|
|
420
440
|
const worktreeResult = await runGit(resolvedProjectDir, [
|
|
@@ -433,7 +453,8 @@ async function buildDashboardGitSnapshot(options) {
|
|
|
433
453
|
worktree,
|
|
434
454
|
defaultBranch,
|
|
435
455
|
runGit,
|
|
436
|
-
maxCommitEntries
|
|
456
|
+
maxCommitEntries,
|
|
457
|
+
readPathTimestampMs
|
|
437
458
|
})));
|
|
438
459
|
worktrees.sort((a, b) => {
|
|
439
460
|
if (a.isCurrent !== b.isCurrent) return a.isCurrent ? -1 : 1;
|
|
@@ -1598,6 +1619,9 @@ const changeRouter = router({
|
|
|
1598
1619
|
if (!await ctx.adapter.toggleTask(input.changeId, input.taskIndex, input.completed)) throw new Error(`Failed to toggle task ${input.taskIndex} in change ${input.changeId}`);
|
|
1599
1620
|
return { success: true };
|
|
1600
1621
|
}),
|
|
1622
|
+
subscribe: publicProcedure.subscription(({ ctx }) => {
|
|
1623
|
+
return createReactiveSubscription(() => ctx.adapter.listChangesWithMeta());
|
|
1624
|
+
}),
|
|
1601
1625
|
subscribeFiles: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
1602
1626
|
return createReactiveSubscriptionWithInput((id) => ctx.adapter.readChangeFiles(id))(input.id);
|
|
1603
1627
|
})
|
|
@@ -2262,8 +2286,8 @@ const dashboardRouter = router({
|
|
|
2262
2286
|
}),
|
|
2263
2287
|
refreshGitSnapshot: publicProcedure.input(z.object({ reason: z.string().optional() }).optional()).mutation(async ({ ctx, input }) => {
|
|
2264
2288
|
const reason = input?.reason?.trim() || "manual-refresh";
|
|
2265
|
-
await touchDashboardGitRefreshStamp(ctx.projectDir, reason);
|
|
2266
2289
|
await ctx.dashboardOverviewService.refresh(reason);
|
|
2290
|
+
await touchDashboardGitRefreshStamp(ctx.projectDir, reason);
|
|
2267
2291
|
return { success: true };
|
|
2268
2292
|
}),
|
|
2269
2293
|
removeDetachedWorktree: publicProcedure.input(z.object({ path: z.string().min(1) })).mutation(async ({ ctx, input }) => {
|
|
@@ -2272,8 +2296,8 @@ const dashboardRouter = router({
|
|
|
2272
2296
|
projectDir: ctx.projectDir,
|
|
2273
2297
|
targetPath: input.path
|
|
2274
2298
|
});
|
|
2275
|
-
await touchDashboardGitRefreshStamp(ctx.projectDir, "remove-detached-worktree");
|
|
2276
2299
|
await ctx.dashboardOverviewService.refresh("remove-detached-worktree");
|
|
2300
|
+
await touchDashboardGitRefreshStamp(ctx.projectDir, "remove-detached-worktree");
|
|
2277
2301
|
return { success: true };
|
|
2278
2302
|
}),
|
|
2279
2303
|
gitTaskStatus: publicProcedure.query(async ({ ctx }) => {
|