@lisang233/pi-sync 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lisang233/pi-sync",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Personal Pi extension that syncs Pi configuration through Git with background auto-sync and git-style fetch/merge conflict handling.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/extension.ts CHANGED
@@ -3,7 +3,7 @@ import type {
3
3
  ExtensionCommandContext,
4
4
  ExtensionContext,
5
5
  } from "@earendil-works/pi-coding-agent";
6
- import { loadConfig } from "./config.js";
6
+ import { loadConfig, type SyncConfig } from "./config.js";
7
7
  import { runConfigEditor } from "./config-ui.js";
8
8
  import * as operations from "./operations.js";
9
9
  import { syncBusyText } from "./status.js";
@@ -38,6 +38,7 @@ const USAGE = [
38
38
  export default function sync(pi: ExtensionAPI): void {
39
39
  let sessionAbort = new AbortController();
40
40
  let backgroundSync: BackgroundSync | undefined;
41
+ let backgroundPush: BackgroundSync | undefined;
41
42
 
42
43
  const startBackgroundSync = (ctx: ExtensionContext, signal: AbortSignal) => {
43
44
  // While the automatic fetch is in flight the indicator shows the busy
@@ -55,15 +56,42 @@ export default function sync(pi: ExtensionAPI): void {
55
56
  backgroundSync = { settled };
56
57
  };
57
58
 
58
- const drainBackgroundSync = async (signal?: AbortSignal): Promise<void> => {
59
- const current = backgroundSync;
59
+ // Publish in the background so the TUI stays interactive while git talks
60
+ // to the remote (fetch + push can take many seconds). The indicator shows
61
+ // "pushing…"; operations.push notifies the outcome when it settles.
62
+ const startBackgroundPush = (
63
+ ctx: ExtensionCommandContext,
64
+ config: SyncConfig,
65
+ force: boolean,
66
+ signal: AbortSignal,
67
+ ) => {
68
+ ctx.ui.setStatus(STATUS_KEY, syncBusyText("push"));
69
+ const settled = (async () => {
70
+ try {
71
+ await operations.push(ctx, config, { force });
72
+ } catch (error) {
73
+ if (signal.aborted) return;
74
+ ctx.ui.setStatus(STATUS_KEY, undefined);
75
+ ctx.ui.notify(`pi-sync push failed: ${errorMessage(error)}`, "error");
76
+ }
77
+ })();
78
+ backgroundPush = { settled };
79
+ };
80
+
81
+ const drainBackgroundTasks = async (signal?: AbortSignal): Promise<void> => {
82
+ const tasks = [backgroundSync, backgroundPush];
60
83
  backgroundSync = undefined;
61
- if (!current) return;
62
- try {
63
- await (signal ? Promise.race([current.settled, waitForAbort(signal)]) : current.settled);
64
- } catch {
65
- // The shutdown deadline or a replacement aborted while draining; the
66
- // background sync observes its own session signal and settles on its own.
84
+ backgroundPush = undefined;
85
+ for (const current of tasks) {
86
+ if (!current) continue;
87
+ try {
88
+ await (signal
89
+ ? Promise.race([current.settled, waitForAbort(signal)])
90
+ : current.settled);
91
+ } catch {
92
+ // The shutdown deadline or a replacement aborted while draining; the
93
+ // background task observes its own session signal and settles on its own.
94
+ }
67
95
  }
68
96
  };
69
97
 
@@ -84,7 +112,18 @@ export default function sync(pi: ExtensionAPI): void {
84
112
  );
85
113
  }
86
114
  try {
87
- await handleCommand(args, ctx);
115
+ await handleCommand(args, ctx, async (pushCtx, config, force) => {
116
+ if (backgroundPush) {
117
+ pushCtx.ui.notify("A push is already in progress.", "warning");
118
+ return;
119
+ }
120
+ // Wait for the session-start automatic fetch so the two
121
+ // background tasks never contend on the mirror repo, then run
122
+ // the publish without blocking the TUI.
123
+ await drainBackgroundTasks();
124
+ if (sessionAbort.signal.aborted) return;
125
+ startBackgroundPush(pushCtx, config, force, sessionAbort.signal);
126
+ });
88
127
  } catch (error) {
89
128
  if (sessionAbort.signal.aborted) return;
90
129
  ctx.ui.setStatus(STATUS_KEY, undefined);
@@ -98,7 +137,7 @@ export default function sync(pi: ExtensionAPI): void {
98
137
  sessionAbort = new AbortController();
99
138
  const signal = sessionAbort.signal;
100
139
  ctx.ui.setStatus(STATUS_KEY, undefined);
101
- await drainBackgroundSync();
140
+ await drainBackgroundTasks();
102
141
  try {
103
142
  const config = await loadConfig();
104
143
  if (signal.aborted) return;
@@ -126,7 +165,15 @@ async function runAutomaticSync(ctx: ExtensionContext, signal: AbortSignal): Pro
126
165
  await operations.fetch(ctx, config, { quiet: true });
127
166
  }
128
167
 
129
- async function handleCommand(rawArgs: string, ctx: ExtensionCommandContext): Promise<void> {
168
+ async function handleCommand(
169
+ rawArgs: string,
170
+ ctx: ExtensionCommandContext,
171
+ runPush: (
172
+ ctx: ExtensionCommandContext,
173
+ config: SyncConfig,
174
+ force: boolean,
175
+ ) => Promise<void>,
176
+ ): Promise<void> {
130
177
  const [first = "", ...restTokens] = rawArgs.trim().split(/\s+/u);
131
178
  const subcommand = normalizeSubcommand(first);
132
179
  if (subcommand === undefined || subcommand === "help") {
@@ -159,7 +206,7 @@ async function handleCommand(rawArgs: string, ctx: ExtensionCommandContext): Pro
159
206
  });
160
207
  return;
161
208
  case "push":
162
- await operations.push(ctx, config, { force });
209
+ await runPush(ctx, config, force);
163
210
  return;
164
211
  case "pull":
165
212
  await operations.pull(ctx, config, {
package/src/status.ts CHANGED
@@ -49,9 +49,9 @@ export function deriveSyncStatus(
49
49
  return { label: "up-to-date", ahead: 0, behind: 0, conflicts: 0 };
50
50
  }
51
51
 
52
- /** Short status-bar text while a background sync (automatic fetch) is in flight. */
53
- export function syncBusyText(): string {
54
- return "sync: fetching…";
52
+ /** Status-bar text while a background/foreground sync action is in flight. */
53
+ export function syncBusyText(action: "fetch" | "push" = "fetch"): string {
54
+ return action === "push" ? "sync: pushing…" : "sync: fetching…";
55
55
  }
56
56
 
57
57
  /** Short status-bar text for the sync indicator. */