hsync-cli 1.1.0 → 1.1.2
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 +0 -0
- package/dist/bin.js +533 -215
- package/dist/web/index.html +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
File without changes
|
package/dist/bin.js
CHANGED
|
@@ -70153,6 +70153,177 @@ class SyncEngine {
|
|
|
70153
70153
|
}
|
|
70154
70154
|
}
|
|
70155
70155
|
// src/commands/sync.ts
|
|
70156
|
+
function getHsyncDir() {
|
|
70157
|
+
return path7.join(os4.homedir(), ".hsync");
|
|
70158
|
+
}
|
|
70159
|
+
function isProcessAlive(pid) {
|
|
70160
|
+
try {
|
|
70161
|
+
process.kill(pid, 0);
|
|
70162
|
+
return true;
|
|
70163
|
+
} catch {
|
|
70164
|
+
return false;
|
|
70165
|
+
}
|
|
70166
|
+
}
|
|
70167
|
+
function jobNameFromPidFile(filename) {
|
|
70168
|
+
if (filename === "daemon.pid") {
|
|
70169
|
+
return "all jobs";
|
|
70170
|
+
}
|
|
70171
|
+
return filename.replace("daemon-", "").replace(".pid", "");
|
|
70172
|
+
}
|
|
70173
|
+
async function readPidRecord(pidFile) {
|
|
70174
|
+
try {
|
|
70175
|
+
const pidText = await fs6.readFile(pidFile, "utf8");
|
|
70176
|
+
const pid = parseInt(pidText.trim(), 10);
|
|
70177
|
+
if (isNaN(pid)) {
|
|
70178
|
+
await fs6.unlink(pidFile);
|
|
70179
|
+
return null;
|
|
70180
|
+
}
|
|
70181
|
+
if (!isProcessAlive(pid)) {
|
|
70182
|
+
await fs6.unlink(pidFile);
|
|
70183
|
+
return null;
|
|
70184
|
+
}
|
|
70185
|
+
return { pid };
|
|
70186
|
+
} catch {
|
|
70187
|
+
return null;
|
|
70188
|
+
}
|
|
70189
|
+
}
|
|
70190
|
+
async function listAllDaemonPids() {
|
|
70191
|
+
const hsyncDir = getHsyncDir();
|
|
70192
|
+
const results = [];
|
|
70193
|
+
try {
|
|
70194
|
+
const files = await fs6.readdir(hsyncDir);
|
|
70195
|
+
for (const file of files) {
|
|
70196
|
+
if (!file.startsWith("daemon") || !file.endsWith(".pid")) {
|
|
70197
|
+
continue;
|
|
70198
|
+
}
|
|
70199
|
+
const pidFile = path7.join(hsyncDir, file);
|
|
70200
|
+
const record = await readPidRecord(pidFile);
|
|
70201
|
+
if (record) {
|
|
70202
|
+
results.push({
|
|
70203
|
+
pidFile,
|
|
70204
|
+
pid: record.pid,
|
|
70205
|
+
jobName: jobNameFromPidFile(file)
|
|
70206
|
+
});
|
|
70207
|
+
}
|
|
70208
|
+
}
|
|
70209
|
+
} catch {}
|
|
70210
|
+
return results;
|
|
70211
|
+
}
|
|
70212
|
+
var RECONCILE_INTERVAL_MS = 1e4;
|
|
70213
|
+
async function isGlobalDaemonRunning() {
|
|
70214
|
+
const globalFile = path7.join(getHsyncDir(), "daemon.pid");
|
|
70215
|
+
const record = await readPidRecord(globalFile);
|
|
70216
|
+
return record !== null;
|
|
70217
|
+
}
|
|
70218
|
+
async function executeSyncPass(jobId, activeJobs) {
|
|
70219
|
+
if (activeJobs.has(jobId)) {
|
|
70220
|
+
const config = await loadConfig();
|
|
70221
|
+
const busyJob = config.syncJobs?.find((j) => j.id === jobId);
|
|
70222
|
+
const busyName = busyJob?.name || jobId;
|
|
70223
|
+
console.log(source_default.yellow(`[${new Date().toLocaleTimeString()}] [Sync: ${busyName}] Skip run — previous run still in progress.`));
|
|
70224
|
+
return;
|
|
70225
|
+
}
|
|
70226
|
+
const freshConfig = await loadConfig();
|
|
70227
|
+
const freshServerUrl = freshConfig.serverUrl;
|
|
70228
|
+
const freshApiKey = freshConfig.apiKey;
|
|
70229
|
+
const freshJob = freshConfig.syncJobs?.find((j) => j.id === jobId);
|
|
70230
|
+
if (!freshJob) {
|
|
70231
|
+
return;
|
|
70232
|
+
}
|
|
70233
|
+
if (!freshServerUrl || !freshApiKey) {
|
|
70234
|
+
console.error(source_default.red(`[${new Date().toLocaleTimeString()}] [Sync: ${freshJob.name}] Missing server URL or API key. Run: hsync login`));
|
|
70235
|
+
return;
|
|
70236
|
+
}
|
|
70237
|
+
if (!freshJob.enabled) {
|
|
70238
|
+
return;
|
|
70239
|
+
}
|
|
70240
|
+
activeJobs.add(jobId);
|
|
70241
|
+
const stateDbPath = path7.join(getHsyncDir(), "state", `${freshJob.id}.db`);
|
|
70242
|
+
const engine = new SyncEngine(freshJob, freshServerUrl, freshApiKey, stateDbPath);
|
|
70243
|
+
let uploadCount = 0;
|
|
70244
|
+
let downloadCount = 0;
|
|
70245
|
+
let skipCount = 0;
|
|
70246
|
+
let errorCount = 0;
|
|
70247
|
+
engine.on((event) => {
|
|
70248
|
+
const time = new Date().toLocaleTimeString();
|
|
70249
|
+
if (event.type === "start") {
|
|
70250
|
+
console.log(source_default.blue(`[${time}] [Sync: ${freshJob.name}] Running scan...`));
|
|
70251
|
+
} else if (event.type === "scan-complete") {
|
|
70252
|
+
console.log(source_default.blue(`[${time}] [Sync: ${freshJob.name}] Scan completed.`));
|
|
70253
|
+
} else if (event.type === "upload-complete") {
|
|
70254
|
+
uploadCount++;
|
|
70255
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Uploaded: ${event.path}`));
|
|
70256
|
+
} else if (event.type === "download-complete") {
|
|
70257
|
+
downloadCount++;
|
|
70258
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Downloaded: ${event.path}`));
|
|
70259
|
+
} else if (event.type === "skip") {
|
|
70260
|
+
skipCount++;
|
|
70261
|
+
} else if (event.type === "conflict") {
|
|
70262
|
+
console.log(source_default.yellow(`[${time}] [Sync: ${freshJob.name}] ⚠ Conflict resolved for: ${event.path}`));
|
|
70263
|
+
} else if (event.type === "error") {
|
|
70264
|
+
errorCount++;
|
|
70265
|
+
console.error(source_default.red(`[${time}] [Sync: ${freshJob.name}] ✘ Error on "${event.path}": ${event.error?.message}`));
|
|
70266
|
+
} else if (event.type === "complete") {
|
|
70267
|
+
const summary = `${uploadCount} uploaded, ${downloadCount} downloaded, ${skipCount} unchanged`;
|
|
70268
|
+
if (uploadCount === 0 && downloadCount === 0 && errorCount === 0) {
|
|
70269
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Sync pass completed (${summary}).`));
|
|
70270
|
+
} else {
|
|
70271
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Sync pass completed successfully (${summary}).`));
|
|
70272
|
+
}
|
|
70273
|
+
}
|
|
70274
|
+
});
|
|
70275
|
+
try {
|
|
70276
|
+
await engine.run();
|
|
70277
|
+
} catch (runErr) {
|
|
70278
|
+
console.error(source_default.red(`[${new Date().toLocaleTimeString()}] [Sync: ${freshJob.name}] Sync crash: ${runErr.message}`));
|
|
70279
|
+
} finally {
|
|
70280
|
+
activeJobs.delete(jobId);
|
|
70281
|
+
}
|
|
70282
|
+
}
|
|
70283
|
+
function scheduleSyncJob(job, activeJobs, scheduledJobs) {
|
|
70284
|
+
const intervalDelay = job.intervalMs || 1e4;
|
|
70285
|
+
const triggerSyncRun = async () => {
|
|
70286
|
+
await executeSyncPass(job.id, activeJobs);
|
|
70287
|
+
};
|
|
70288
|
+
triggerSyncRun();
|
|
70289
|
+
const intervalId = setInterval(triggerSyncRun, intervalDelay);
|
|
70290
|
+
scheduledJobs.set(job.id, { intervalId, intervalMs: intervalDelay });
|
|
70291
|
+
console.log(source_default.green(`[${new Date().toLocaleTimeString()}] [Scheduler] Registered: ${job.name} (every ${intervalDelay / 1000}s)`));
|
|
70292
|
+
}
|
|
70293
|
+
function unscheduleSyncJob(jobId, jobName, scheduledJobs) {
|
|
70294
|
+
const entry = scheduledJobs.get(jobId);
|
|
70295
|
+
if (!entry) {
|
|
70296
|
+
return;
|
|
70297
|
+
}
|
|
70298
|
+
clearInterval(entry.intervalId);
|
|
70299
|
+
scheduledJobs.delete(jobId);
|
|
70300
|
+
console.log(source_default.yellow(`[${new Date().toLocaleTimeString()}] [Scheduler] Unregistered: ${jobName || jobId}`));
|
|
70301
|
+
}
|
|
70302
|
+
async function reconcileSchedules(activeJobs, scheduledJobs, singleJobName) {
|
|
70303
|
+
const config = await loadConfig();
|
|
70304
|
+
let jobs = (config.syncJobs || []).filter((j) => j.enabled);
|
|
70305
|
+
if (singleJobName) {
|
|
70306
|
+
jobs = jobs.filter((j) => j.name === singleJobName);
|
|
70307
|
+
}
|
|
70308
|
+
const enabledIds = new Set(jobs.map((j) => j.id));
|
|
70309
|
+
for (const [jobId, entry] of scheduledJobs) {
|
|
70310
|
+
if (!enabledIds.has(jobId)) {
|
|
70311
|
+
const staleJob = config.syncJobs?.find((j) => j.id === jobId);
|
|
70312
|
+
unscheduleSyncJob(jobId, staleJob?.name, scheduledJobs);
|
|
70313
|
+
continue;
|
|
70314
|
+
}
|
|
70315
|
+
const job = jobs.find((j) => j.id === jobId);
|
|
70316
|
+
if (job && entry.intervalMs !== (job.intervalMs || 1e4)) {
|
|
70317
|
+
unscheduleSyncJob(jobId, job.name, scheduledJobs);
|
|
70318
|
+
scheduleSyncJob(job, activeJobs, scheduledJobs);
|
|
70319
|
+
}
|
|
70320
|
+
}
|
|
70321
|
+
for (const job of jobs) {
|
|
70322
|
+
if (!scheduledJobs.has(job.id)) {
|
|
70323
|
+
scheduleSyncJob(job, activeJobs, scheduledJobs);
|
|
70324
|
+
}
|
|
70325
|
+
}
|
|
70326
|
+
}
|
|
70156
70327
|
function parseRemoteTarget2(target) {
|
|
70157
70328
|
const parts = target.split(":");
|
|
70158
70329
|
const slug = parts[0];
|
|
@@ -70201,6 +70372,11 @@ async function addSyncJob(localPath, remoteTarget, options) {
|
|
|
70201
70372
|
console.log(`Direction: ${newJob.direction}`);
|
|
70202
70373
|
console.log(`Strategy: ${newJob.conflictStrategy}`);
|
|
70203
70374
|
console.log(`Interval: ${intervalSec} seconds`);
|
|
70375
|
+
if (await isGlobalDaemonRunning()) {
|
|
70376
|
+
console.log(source_default.gray("The running sync daemon will pick up this job automatically."));
|
|
70377
|
+
} else {
|
|
70378
|
+
console.log(source_default.gray("Start all sync jobs with: hsync sync start --daemon"));
|
|
70379
|
+
}
|
|
70204
70380
|
} catch (err) {
|
|
70205
70381
|
console.error(source_default.red(`Error: Failed to register sync job: ${err.message}`));
|
|
70206
70382
|
process.exit(1);
|
|
@@ -70249,37 +70425,44 @@ async function removeSyncJob(name) {
|
|
|
70249
70425
|
}
|
|
70250
70426
|
async function startSyncScheduler(name, options) {
|
|
70251
70427
|
const isDaemon = options?.daemon;
|
|
70252
|
-
|
|
70253
|
-
const
|
|
70428
|
+
let foregroundJobName = name;
|
|
70429
|
+
const hsyncDir = getHsyncDir();
|
|
70430
|
+
const globalPidFile = path7.join(hsyncDir, "daemon.pid");
|
|
70431
|
+
if (isDaemon && foregroundJobName) {
|
|
70432
|
+
console.log(source_default.yellow(`The sync daemon manages all enabled jobs. Starting global daemon instead of "${foregroundJobName}" only.`));
|
|
70433
|
+
foregroundJobName = undefined;
|
|
70434
|
+
}
|
|
70254
70435
|
if (isDaemon) {
|
|
70255
70436
|
await fs6.mkdir(hsyncDir, { recursive: true });
|
|
70256
|
-
|
|
70257
|
-
|
|
70258
|
-
|
|
70259
|
-
|
|
70260
|
-
|
|
70261
|
-
|
|
70262
|
-
|
|
70263
|
-
|
|
70264
|
-
|
|
70265
|
-
|
|
70266
|
-
}
|
|
70267
|
-
const logFile = path7.join(hsyncDir,
|
|
70268
|
-
console.log(source_default.cyan(`⚙️ Starting sync daemon in the background...`));
|
|
70269
|
-
const
|
|
70270
|
-
const
|
|
70271
|
-
const
|
|
70437
|
+
const existing = await readPidRecord(globalPidFile);
|
|
70438
|
+
if (existing) {
|
|
70439
|
+
console.log(source_default.yellow(`Warning: Sync daemon is already running (PID: ${existing.pid}).`));
|
|
70440
|
+
console.log(source_default.gray("New sync jobs are picked up automatically — no restart needed."));
|
|
70441
|
+
return;
|
|
70442
|
+
}
|
|
70443
|
+
const legacyDaemons = await listAllDaemonPids();
|
|
70444
|
+
const perJobDaemons = legacyDaemons.filter((d) => d.jobName !== "all jobs");
|
|
70445
|
+
if (perJobDaemons.length > 0) {
|
|
70446
|
+
console.log(source_default.yellow("Warning: Legacy per-job daemons are still running. Stop them with: hsync sync stop"));
|
|
70447
|
+
}
|
|
70448
|
+
const logFile = path7.join(hsyncDir, "daemon.log");
|
|
70449
|
+
console.log(source_default.cyan(`⚙️ Starting global sync daemon in the background...`));
|
|
70450
|
+
const bunExec = process.execPath;
|
|
70451
|
+
const scriptPath = Bun.main;
|
|
70452
|
+
const spawnArgs = ["sync", "start"];
|
|
70453
|
+
const logFileHandle = await fs6.open(logFile, "a");
|
|
70454
|
+
const subprocess = Bun.spawn([bunExec, scriptPath, ...spawnArgs], {
|
|
70272
70455
|
detached: true,
|
|
70273
70456
|
stdout: logFileHandle.fd,
|
|
70274
70457
|
stderr: logFileHandle.fd,
|
|
70275
70458
|
stdin: "ignore"
|
|
70276
70459
|
});
|
|
70277
|
-
const pid = subprocess.pid;
|
|
70278
|
-
await fs6.writeFile(pidFile, pid.toString(), "utf8");
|
|
70279
70460
|
subprocess.unref();
|
|
70280
|
-
|
|
70281
|
-
console.log(
|
|
70282
|
-
console.log(
|
|
70461
|
+
await logFileHandle.close();
|
|
70462
|
+
console.log(source_default.green(`✔ Global sync daemon started successfully!`));
|
|
70463
|
+
console.log(source_default.gray("All enabled sync jobs will be scheduled automatically."));
|
|
70464
|
+
console.log(`Logs: hsync sync logs -f`);
|
|
70465
|
+
console.log(source_default.gray("Stop with: hsync sync stop"));
|
|
70283
70466
|
return;
|
|
70284
70467
|
}
|
|
70285
70468
|
try {
|
|
@@ -70290,11 +70473,12 @@ async function startSyncScheduler(name, options) {
|
|
|
70290
70473
|
console.error(source_default.red("Error: CLI not authenticated. Please run: hsync config set-server <url> && hsync login"));
|
|
70291
70474
|
process.exit(1);
|
|
70292
70475
|
}
|
|
70476
|
+
const isGlobalScheduler = !foregroundJobName;
|
|
70293
70477
|
let jobs = config.syncJobs || [];
|
|
70294
|
-
if (
|
|
70295
|
-
jobs = jobs.filter((j) => j.name ===
|
|
70478
|
+
if (foregroundJobName) {
|
|
70479
|
+
jobs = jobs.filter((j) => j.name === foregroundJobName);
|
|
70296
70480
|
if (jobs.length === 0) {
|
|
70297
|
-
console.error(source_default.red(`Error: Sync job named "${
|
|
70481
|
+
console.error(source_default.red(`Error: Sync job named "${foregroundJobName}" was not found or is disabled.`));
|
|
70298
70482
|
process.exit(1);
|
|
70299
70483
|
}
|
|
70300
70484
|
} else {
|
|
@@ -70305,10 +70489,10 @@ async function startSyncScheduler(name, options) {
|
|
|
70305
70489
|
return;
|
|
70306
70490
|
}
|
|
70307
70491
|
await fs6.mkdir(hsyncDir, { recursive: true });
|
|
70308
|
-
await fs6.writeFile(
|
|
70492
|
+
await fs6.writeFile(globalPidFile, process.pid.toString(), "utf8");
|
|
70309
70493
|
const cleanPidFile = async () => {
|
|
70310
70494
|
try {
|
|
70311
|
-
await fs6.unlink(
|
|
70495
|
+
await fs6.unlink(globalPidFile);
|
|
70312
70496
|
} catch {}
|
|
70313
70497
|
};
|
|
70314
70498
|
process.on("SIGINT", async () => {
|
|
@@ -70322,89 +70506,86 @@ async function startSyncScheduler(name, options) {
|
|
|
70322
70506
|
console.log(source_default.cyan(`
|
|
70323
70507
|
=== Starting Sync Scheduler Service ===`));
|
|
70324
70508
|
console.log(source_default.cyan(`Target Server: ${serverUrl}`));
|
|
70325
|
-
|
|
70509
|
+
if (isGlobalScheduler) {
|
|
70510
|
+
console.log(source_default.cyan(`Mode: Global daemon (all enabled jobs, auto-discovers new jobs)`));
|
|
70511
|
+
console.log(source_default.cyan(`Currently registered: ${jobs.map((j) => j.name).join(", ")}
|
|
70326
70512
|
`));
|
|
70513
|
+
} else {
|
|
70514
|
+
console.log(source_default.cyan(`Active Profile: ${jobs.map((j) => j.name).join(", ")}
|
|
70515
|
+
`));
|
|
70516
|
+
}
|
|
70327
70517
|
const activeJobs = new Set;
|
|
70328
|
-
|
|
70329
|
-
|
|
70330
|
-
|
|
70331
|
-
|
|
70332
|
-
|
|
70333
|
-
console.
|
|
70334
|
-
return;
|
|
70335
|
-
}
|
|
70336
|
-
activeJobs.add(job.id);
|
|
70337
|
-
const engine = new SyncEngine(job, serverUrl, apiKey, stateDbPath);
|
|
70338
|
-
engine.on((event) => {
|
|
70339
|
-
const time = new Date().toLocaleTimeString();
|
|
70340
|
-
if (event.type === "start") {
|
|
70341
|
-
console.log(source_default.blue(`[${time}] [Sync: ${job.name}] Running scan...`));
|
|
70342
|
-
} else if (event.type === "scan-complete") {
|
|
70343
|
-
console.log(source_default.blue(`[${time}] [Sync: ${job.name}] Scan completed.`));
|
|
70344
|
-
} else if (event.type === "upload-complete") {
|
|
70345
|
-
console.log(source_default.green(`[${time}] [Sync: ${job.name}] ✔ Uploaded: ${event.path}`));
|
|
70346
|
-
} else if (event.type === "download-complete") {
|
|
70347
|
-
console.log(source_default.green(`[${time}] [Sync: ${job.name}] ✔ Downloaded: ${event.path}`));
|
|
70348
|
-
} else if (event.type === "conflict") {
|
|
70349
|
-
console.log(source_default.yellow(`[${time}] [Sync: ${job.name}] ⚠ Conflict resolved for: ${event.path}`));
|
|
70350
|
-
} else if (event.type === "error") {
|
|
70351
|
-
console.error(source_default.red(`[${time}] [Sync: ${job.name}] ✘ Error on "${event.path}": ${event.error?.message}`));
|
|
70352
|
-
} else if (event.type === "complete") {
|
|
70353
|
-
console.log(source_default.green(`[${time}] [Sync: ${job.name}] ✔ Sync pass completed successfully.`));
|
|
70354
|
-
}
|
|
70518
|
+
const scheduledJobs = new Map;
|
|
70519
|
+
await reconcileSchedules(activeJobs, scheduledJobs, foregroundJobName);
|
|
70520
|
+
if (isGlobalScheduler) {
|
|
70521
|
+
setInterval(() => {
|
|
70522
|
+
reconcileSchedules(activeJobs, scheduledJobs).catch((reconcileErr) => {
|
|
70523
|
+
console.error(source_default.red(`[Scheduler] Failed to refresh job list: ${reconcileErr.message}`));
|
|
70355
70524
|
});
|
|
70356
|
-
|
|
70357
|
-
await engine.run();
|
|
70358
|
-
} catch (runErr) {
|
|
70359
|
-
console.error(source_default.red(`[${new Date().toLocaleTimeString()}] [Sync: ${job.name}] Sync crash: ${runErr.message}`));
|
|
70360
|
-
} finally {
|
|
70361
|
-
activeJobs.delete(job.id);
|
|
70362
|
-
}
|
|
70363
|
-
};
|
|
70364
|
-
triggerSyncRun();
|
|
70365
|
-
setInterval(triggerSyncRun, intervalDelay);
|
|
70525
|
+
}, RECONCILE_INTERVAL_MS);
|
|
70366
70526
|
}
|
|
70367
70527
|
} catch (err) {
|
|
70368
70528
|
console.error(source_default.red(`Scheduler crashed: ${err.message}`));
|
|
70369
70529
|
process.exit(1);
|
|
70370
70530
|
}
|
|
70371
70531
|
}
|
|
70532
|
+
async function showSyncStatus() {
|
|
70533
|
+
try {
|
|
70534
|
+
const globalFile = path7.join(getHsyncDir(), "daemon.pid");
|
|
70535
|
+
const globalDaemon = await readPidRecord(globalFile);
|
|
70536
|
+
const config = await loadConfig();
|
|
70537
|
+
const enabledJobs = (config.syncJobs || []).filter((j) => j.enabled);
|
|
70538
|
+
if (!globalDaemon) {
|
|
70539
|
+
console.log(source_default.yellow("Global sync daemon is not running."));
|
|
70540
|
+
console.log(source_default.gray("Start it with: hsync sync start --daemon"));
|
|
70541
|
+
if (enabledJobs.length > 0) {
|
|
70542
|
+
console.log(source_default.gray(`Registered jobs waiting: ${enabledJobs.map((j) => j.name).join(", ")}`));
|
|
70543
|
+
}
|
|
70544
|
+
return;
|
|
70545
|
+
}
|
|
70546
|
+
console.log(source_default.cyan(`
|
|
70547
|
+
=== Global Sync Daemon ===`));
|
|
70548
|
+
console.log(`Status: ${source_default.green("running")} (PID: ${globalDaemon.pid})`);
|
|
70549
|
+
if (enabledJobs.length === 0) {
|
|
70550
|
+
console.log(source_default.yellow("No enabled sync jobs registered yet."));
|
|
70551
|
+
} else {
|
|
70552
|
+
console.log(`Jobs: ${enabledJobs.map((j) => j.name).join(", ")}`);
|
|
70553
|
+
console.log(source_default.gray("New jobs are picked up automatically within ~10 seconds."));
|
|
70554
|
+
}
|
|
70555
|
+
console.log();
|
|
70556
|
+
} catch (err) {
|
|
70557
|
+
console.error(source_default.red(`Error checking sync status: ${err.message}`));
|
|
70558
|
+
}
|
|
70559
|
+
}
|
|
70372
70560
|
async function stopSyncScheduler(name) {
|
|
70373
70561
|
try {
|
|
70374
|
-
const hsyncDir = path7.join(os4.homedir(), ".hsync");
|
|
70375
70562
|
if (name) {
|
|
70376
|
-
|
|
70563
|
+
console.log(source_default.yellow(`The sync daemon manages all jobs together. Stopping global daemon (includes "${name}")...`));
|
|
70564
|
+
}
|
|
70565
|
+
const globalFile = path7.join(getHsyncDir(), "daemon.pid");
|
|
70566
|
+
const globalDaemon = await readPidRecord(globalFile);
|
|
70567
|
+
if (globalDaemon) {
|
|
70568
|
+
process.kill(globalDaemon.pid, "SIGTERM");
|
|
70377
70569
|
try {
|
|
70378
|
-
|
|
70379
|
-
|
|
70380
|
-
|
|
70381
|
-
await fs6.unlink(pidFile);
|
|
70382
|
-
console.log(source_default.green(`✔ Sync daemon for "${name}" (PID: ${pid}) has been stopped.`));
|
|
70383
|
-
} catch {
|
|
70384
|
-
console.error(source_default.red(`Error: No active sync daemon found for "${name}".`));
|
|
70385
|
-
}
|
|
70570
|
+
await fs6.unlink(globalFile);
|
|
70571
|
+
} catch {}
|
|
70572
|
+
console.log(source_default.green(`✔ Global sync daemon stopped (PID: ${globalDaemon.pid}).`));
|
|
70386
70573
|
} else {
|
|
70387
|
-
|
|
70388
|
-
|
|
70389
|
-
|
|
70390
|
-
|
|
70391
|
-
|
|
70392
|
-
|
|
70393
|
-
|
|
70394
|
-
const pidFile = path7.join(hsyncDir, file);
|
|
70395
|
-
try {
|
|
70396
|
-
const pidText = await fs6.readFile(pidFile, "utf8");
|
|
70397
|
-
const pid = parseInt(pidText.trim(), 10);
|
|
70398
|
-
process.kill(pid, "SIGTERM");
|
|
70399
|
-
await fs6.unlink(pidFile);
|
|
70400
|
-
const jobName = file.replace("daemon-", "").replace(".pid", "");
|
|
70401
|
-
console.log(source_default.green(`✔ Stopped sync daemon (PID: ${pid}) for ${jobName === "daemon" ? "all jobs" : jobName}.`));
|
|
70402
|
-
} catch {
|
|
70403
|
-
try {
|
|
70404
|
-
await fs6.unlink(pidFile);
|
|
70405
|
-
} catch {}
|
|
70406
|
-
}
|
|
70574
|
+
console.log(source_default.yellow("Global sync daemon is not running."));
|
|
70575
|
+
console.log(source_default.gray("Check with: hsync sync status"));
|
|
70576
|
+
}
|
|
70577
|
+
const legacyDaemons = await listAllDaemonPids();
|
|
70578
|
+
for (const daemon of legacyDaemons) {
|
|
70579
|
+
if (daemon.jobName === "all jobs") {
|
|
70580
|
+
continue;
|
|
70407
70581
|
}
|
|
70582
|
+
try {
|
|
70583
|
+
process.kill(daemon.pid, "SIGTERM");
|
|
70584
|
+
} catch {}
|
|
70585
|
+
try {
|
|
70586
|
+
await fs6.unlink(daemon.pidFile);
|
|
70587
|
+
} catch {}
|
|
70588
|
+
console.log(source_default.green(`✔ Stopped legacy per-job daemon (PID: ${daemon.pid}) for ${daemon.jobName}.`));
|
|
70408
70589
|
}
|
|
70409
70590
|
} catch (err) {
|
|
70410
70591
|
console.error(source_default.red(`Error stopping daemon: ${err.message}`));
|
|
@@ -70583,6 +70764,172 @@ import { exec } from "child_process";
|
|
|
70583
70764
|
import fs7 from "fs/promises";
|
|
70584
70765
|
import path8 from "path";
|
|
70585
70766
|
import os5 from "os";
|
|
70767
|
+
function getHsyncDir2() {
|
|
70768
|
+
return path8.join(os5.homedir(), ".hsync");
|
|
70769
|
+
}
|
|
70770
|
+
function isProcessAlive2(pid) {
|
|
70771
|
+
try {
|
|
70772
|
+
process.kill(pid, 0);
|
|
70773
|
+
return true;
|
|
70774
|
+
} catch {
|
|
70775
|
+
return false;
|
|
70776
|
+
}
|
|
70777
|
+
}
|
|
70778
|
+
function jobNameFromPidFile2(filename) {
|
|
70779
|
+
if (filename === "daemon.pid") {
|
|
70780
|
+
return "all jobs";
|
|
70781
|
+
}
|
|
70782
|
+
return filename.replace("daemon-", "").replace(".pid", "");
|
|
70783
|
+
}
|
|
70784
|
+
async function readPidRecord2(pidFile) {
|
|
70785
|
+
try {
|
|
70786
|
+
const pidText = await fs7.readFile(pidFile, "utf8");
|
|
70787
|
+
const pid = parseInt(pidText.trim(), 10);
|
|
70788
|
+
if (isNaN(pid)) {
|
|
70789
|
+
await fs7.unlink(pidFile);
|
|
70790
|
+
return null;
|
|
70791
|
+
}
|
|
70792
|
+
if (!isProcessAlive2(pid)) {
|
|
70793
|
+
await fs7.unlink(pidFile);
|
|
70794
|
+
return null;
|
|
70795
|
+
}
|
|
70796
|
+
return { pid };
|
|
70797
|
+
} catch {
|
|
70798
|
+
return null;
|
|
70799
|
+
}
|
|
70800
|
+
}
|
|
70801
|
+
async function listAllDaemonPids2() {
|
|
70802
|
+
const hsyncDir = getHsyncDir2();
|
|
70803
|
+
const results = [];
|
|
70804
|
+
try {
|
|
70805
|
+
const files = await fs7.readdir(hsyncDir);
|
|
70806
|
+
for (const file of files) {
|
|
70807
|
+
if (!file.startsWith("daemon") || !file.endsWith(".pid")) {
|
|
70808
|
+
continue;
|
|
70809
|
+
}
|
|
70810
|
+
const pidFile = path8.join(hsyncDir, file);
|
|
70811
|
+
const record = await readPidRecord2(pidFile);
|
|
70812
|
+
if (record) {
|
|
70813
|
+
results.push({
|
|
70814
|
+
pidFile,
|
|
70815
|
+
pid: record.pid,
|
|
70816
|
+
jobName: jobNameFromPidFile2(file)
|
|
70817
|
+
});
|
|
70818
|
+
}
|
|
70819
|
+
}
|
|
70820
|
+
} catch {}
|
|
70821
|
+
return results;
|
|
70822
|
+
}
|
|
70823
|
+
var RECONCILE_INTERVAL_MS2 = 1e4;
|
|
70824
|
+
async function executeSyncPass2(jobId, activeJobs) {
|
|
70825
|
+
if (activeJobs.has(jobId)) {
|
|
70826
|
+
const config = await loadConfig();
|
|
70827
|
+
const busyJob = config.syncJobs?.find((j) => j.id === jobId);
|
|
70828
|
+
const busyName = busyJob?.name || jobId;
|
|
70829
|
+
console.log(source_default.yellow(`[${new Date().toLocaleTimeString()}] [Sync: ${busyName}] Skip run — previous run still in progress.`));
|
|
70830
|
+
return;
|
|
70831
|
+
}
|
|
70832
|
+
const freshConfig = await loadConfig();
|
|
70833
|
+
const freshServerUrl = freshConfig.serverUrl;
|
|
70834
|
+
const freshApiKey = freshConfig.apiKey;
|
|
70835
|
+
const freshJob = freshConfig.syncJobs?.find((j) => j.id === jobId);
|
|
70836
|
+
if (!freshJob) {
|
|
70837
|
+
return;
|
|
70838
|
+
}
|
|
70839
|
+
if (!freshServerUrl || !freshApiKey) {
|
|
70840
|
+
console.error(source_default.red(`[${new Date().toLocaleTimeString()}] [Sync: ${freshJob.name}] Missing server URL or API key. Run: hsync login`));
|
|
70841
|
+
return;
|
|
70842
|
+
}
|
|
70843
|
+
if (!freshJob.enabled) {
|
|
70844
|
+
return;
|
|
70845
|
+
}
|
|
70846
|
+
activeJobs.add(jobId);
|
|
70847
|
+
const stateDbPath = path8.join(getHsyncDir2(), "state", `${freshJob.id}.db`);
|
|
70848
|
+
const engine = new SyncEngine(freshJob, freshServerUrl, freshApiKey, stateDbPath);
|
|
70849
|
+
let uploadCount = 0;
|
|
70850
|
+
let downloadCount = 0;
|
|
70851
|
+
let skipCount = 0;
|
|
70852
|
+
let errorCount = 0;
|
|
70853
|
+
engine.on((event) => {
|
|
70854
|
+
const time = new Date().toLocaleTimeString();
|
|
70855
|
+
if (event.type === "start") {
|
|
70856
|
+
console.log(source_default.blue(`[${time}] [Sync: ${freshJob.name}] Running scan...`));
|
|
70857
|
+
} else if (event.type === "scan-complete") {
|
|
70858
|
+
console.log(source_default.blue(`[${time}] [Sync: ${freshJob.name}] Scan completed.`));
|
|
70859
|
+
} else if (event.type === "upload-complete") {
|
|
70860
|
+
uploadCount++;
|
|
70861
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Uploaded: ${event.path}`));
|
|
70862
|
+
} else if (event.type === "download-complete") {
|
|
70863
|
+
downloadCount++;
|
|
70864
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Downloaded: ${event.path}`));
|
|
70865
|
+
} else if (event.type === "skip") {
|
|
70866
|
+
skipCount++;
|
|
70867
|
+
} else if (event.type === "conflict") {
|
|
70868
|
+
console.log(source_default.yellow(`[${time}] [Sync: ${freshJob.name}] ⚠ Conflict resolved for: ${event.path}`));
|
|
70869
|
+
} else if (event.type === "error") {
|
|
70870
|
+
errorCount++;
|
|
70871
|
+
console.error(source_default.red(`[${time}] [Sync: ${freshJob.name}] ✘ Error on "${event.path}": ${event.error?.message}`));
|
|
70872
|
+
} else if (event.type === "complete") {
|
|
70873
|
+
const summary = `${uploadCount} uploaded, ${downloadCount} downloaded, ${skipCount} unchanged`;
|
|
70874
|
+
if (uploadCount === 0 && downloadCount === 0 && errorCount === 0) {
|
|
70875
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Sync pass completed (${summary}).`));
|
|
70876
|
+
} else {
|
|
70877
|
+
console.log(source_default.green(`[${time}] [Sync: ${freshJob.name}] ✔ Sync pass completed successfully (${summary}).`));
|
|
70878
|
+
}
|
|
70879
|
+
}
|
|
70880
|
+
});
|
|
70881
|
+
try {
|
|
70882
|
+
await engine.run();
|
|
70883
|
+
} catch (runErr) {
|
|
70884
|
+
console.error(source_default.red(`[${new Date().toLocaleTimeString()}] [Sync: ${freshJob.name}] Sync crash: ${runErr.message}`));
|
|
70885
|
+
} finally {
|
|
70886
|
+
activeJobs.delete(jobId);
|
|
70887
|
+
}
|
|
70888
|
+
}
|
|
70889
|
+
function scheduleSyncJob2(job, activeJobs, scheduledJobs) {
|
|
70890
|
+
const intervalDelay = job.intervalMs || 1e4;
|
|
70891
|
+
const triggerSyncRun = async () => {
|
|
70892
|
+
await executeSyncPass2(job.id, activeJobs);
|
|
70893
|
+
};
|
|
70894
|
+
triggerSyncRun();
|
|
70895
|
+
const intervalId = setInterval(triggerSyncRun, intervalDelay);
|
|
70896
|
+
scheduledJobs.set(job.id, { intervalId, intervalMs: intervalDelay });
|
|
70897
|
+
console.log(source_default.green(`[${new Date().toLocaleTimeString()}] [Scheduler] Registered: ${job.name} (every ${intervalDelay / 1000}s)`));
|
|
70898
|
+
}
|
|
70899
|
+
function unscheduleSyncJob2(jobId, jobName, scheduledJobs) {
|
|
70900
|
+
const entry = scheduledJobs.get(jobId);
|
|
70901
|
+
if (!entry) {
|
|
70902
|
+
return;
|
|
70903
|
+
}
|
|
70904
|
+
clearInterval(entry.intervalId);
|
|
70905
|
+
scheduledJobs.delete(jobId);
|
|
70906
|
+
console.log(source_default.yellow(`[${new Date().toLocaleTimeString()}] [Scheduler] Unregistered: ${jobName || jobId}`));
|
|
70907
|
+
}
|
|
70908
|
+
async function reconcileSchedules2(activeJobs, scheduledJobs, singleJobName) {
|
|
70909
|
+
const config = await loadConfig();
|
|
70910
|
+
let jobs = (config.syncJobs || []).filter((j) => j.enabled);
|
|
70911
|
+
if (singleJobName) {
|
|
70912
|
+
jobs = jobs.filter((j) => j.name === singleJobName);
|
|
70913
|
+
}
|
|
70914
|
+
const enabledIds = new Set(jobs.map((j) => j.id));
|
|
70915
|
+
for (const [jobId, entry] of scheduledJobs) {
|
|
70916
|
+
if (!enabledIds.has(jobId)) {
|
|
70917
|
+
const staleJob = config.syncJobs?.find((j) => j.id === jobId);
|
|
70918
|
+
unscheduleSyncJob2(jobId, staleJob?.name, scheduledJobs);
|
|
70919
|
+
continue;
|
|
70920
|
+
}
|
|
70921
|
+
const job = jobs.find((j) => j.id === jobId);
|
|
70922
|
+
if (job && entry.intervalMs !== (job.intervalMs || 1e4)) {
|
|
70923
|
+
unscheduleSyncJob2(jobId, job.name, scheduledJobs);
|
|
70924
|
+
scheduleSyncJob2(job, activeJobs, scheduledJobs);
|
|
70925
|
+
}
|
|
70926
|
+
}
|
|
70927
|
+
for (const job of jobs) {
|
|
70928
|
+
if (!scheduledJobs.has(job.id)) {
|
|
70929
|
+
scheduleSyncJob2(job, activeJobs, scheduledJobs);
|
|
70930
|
+
}
|
|
70931
|
+
}
|
|
70932
|
+
}
|
|
70586
70933
|
function parseRemoteTarget3(target) {
|
|
70587
70934
|
const parts = target.split(":");
|
|
70588
70935
|
const slug = parts[0];
|
|
@@ -70591,37 +70938,44 @@ function parseRemoteTarget3(target) {
|
|
|
70591
70938
|
}
|
|
70592
70939
|
async function startSyncScheduler2(name, options) {
|
|
70593
70940
|
const isDaemon = options?.daemon;
|
|
70594
|
-
|
|
70595
|
-
const
|
|
70941
|
+
let foregroundJobName = name;
|
|
70942
|
+
const hsyncDir = getHsyncDir2();
|
|
70943
|
+
const globalPidFile = path8.join(hsyncDir, "daemon.pid");
|
|
70944
|
+
if (isDaemon && foregroundJobName) {
|
|
70945
|
+
console.log(source_default.yellow(`The sync daemon manages all enabled jobs. Starting global daemon instead of "${foregroundJobName}" only.`));
|
|
70946
|
+
foregroundJobName = undefined;
|
|
70947
|
+
}
|
|
70596
70948
|
if (isDaemon) {
|
|
70597
70949
|
await fs7.mkdir(hsyncDir, { recursive: true });
|
|
70598
|
-
|
|
70599
|
-
|
|
70600
|
-
|
|
70601
|
-
|
|
70602
|
-
|
|
70603
|
-
|
|
70604
|
-
|
|
70605
|
-
|
|
70606
|
-
|
|
70607
|
-
|
|
70608
|
-
}
|
|
70609
|
-
const logFile = path8.join(hsyncDir,
|
|
70610
|
-
console.log(source_default.cyan(`⚙️ Starting sync daemon in the background...`));
|
|
70611
|
-
const
|
|
70612
|
-
const
|
|
70613
|
-
const
|
|
70950
|
+
const existing = await readPidRecord2(globalPidFile);
|
|
70951
|
+
if (existing) {
|
|
70952
|
+
console.log(source_default.yellow(`Warning: Sync daemon is already running (PID: ${existing.pid}).`));
|
|
70953
|
+
console.log(source_default.gray("New sync jobs are picked up automatically — no restart needed."));
|
|
70954
|
+
return;
|
|
70955
|
+
}
|
|
70956
|
+
const legacyDaemons = await listAllDaemonPids2();
|
|
70957
|
+
const perJobDaemons = legacyDaemons.filter((d) => d.jobName !== "all jobs");
|
|
70958
|
+
if (perJobDaemons.length > 0) {
|
|
70959
|
+
console.log(source_default.yellow("Warning: Legacy per-job daemons are still running. Stop them with: hsync sync stop"));
|
|
70960
|
+
}
|
|
70961
|
+
const logFile = path8.join(hsyncDir, "daemon.log");
|
|
70962
|
+
console.log(source_default.cyan(`⚙️ Starting global sync daemon in the background...`));
|
|
70963
|
+
const bunExec = process.execPath;
|
|
70964
|
+
const scriptPath = Bun.main;
|
|
70965
|
+
const spawnArgs = ["sync", "start"];
|
|
70966
|
+
const logFileHandle = await fs7.open(logFile, "a");
|
|
70967
|
+
const subprocess = Bun.spawn([bunExec, scriptPath, ...spawnArgs], {
|
|
70614
70968
|
detached: true,
|
|
70615
70969
|
stdout: logFileHandle.fd,
|
|
70616
70970
|
stderr: logFileHandle.fd,
|
|
70617
70971
|
stdin: "ignore"
|
|
70618
70972
|
});
|
|
70619
|
-
const pid = subprocess.pid;
|
|
70620
|
-
await fs7.writeFile(pidFile, pid.toString(), "utf8");
|
|
70621
70973
|
subprocess.unref();
|
|
70622
|
-
|
|
70623
|
-
console.log(
|
|
70624
|
-
console.log(
|
|
70974
|
+
await logFileHandle.close();
|
|
70975
|
+
console.log(source_default.green(`✔ Global sync daemon started successfully!`));
|
|
70976
|
+
console.log(source_default.gray("All enabled sync jobs will be scheduled automatically."));
|
|
70977
|
+
console.log(`Logs: hsync sync logs -f`);
|
|
70978
|
+
console.log(source_default.gray("Stop with: hsync sync stop"));
|
|
70625
70979
|
return;
|
|
70626
70980
|
}
|
|
70627
70981
|
try {
|
|
@@ -70632,11 +70986,12 @@ async function startSyncScheduler2(name, options) {
|
|
|
70632
70986
|
console.error(source_default.red("Error: CLI not authenticated. Please run: hsync config set-server <url> && hsync login"));
|
|
70633
70987
|
process.exit(1);
|
|
70634
70988
|
}
|
|
70989
|
+
const isGlobalScheduler = !foregroundJobName;
|
|
70635
70990
|
let jobs = config.syncJobs || [];
|
|
70636
|
-
if (
|
|
70637
|
-
jobs = jobs.filter((j) => j.name ===
|
|
70991
|
+
if (foregroundJobName) {
|
|
70992
|
+
jobs = jobs.filter((j) => j.name === foregroundJobName);
|
|
70638
70993
|
if (jobs.length === 0) {
|
|
70639
|
-
console.error(source_default.red(`Error: Sync job named "${
|
|
70994
|
+
console.error(source_default.red(`Error: Sync job named "${foregroundJobName}" was not found or is disabled.`));
|
|
70640
70995
|
process.exit(1);
|
|
70641
70996
|
}
|
|
70642
70997
|
} else {
|
|
@@ -70647,10 +71002,10 @@ async function startSyncScheduler2(name, options) {
|
|
|
70647
71002
|
return;
|
|
70648
71003
|
}
|
|
70649
71004
|
await fs7.mkdir(hsyncDir, { recursive: true });
|
|
70650
|
-
await fs7.writeFile(
|
|
71005
|
+
await fs7.writeFile(globalPidFile, process.pid.toString(), "utf8");
|
|
70651
71006
|
const cleanPidFile = async () => {
|
|
70652
71007
|
try {
|
|
70653
|
-
await fs7.unlink(
|
|
71008
|
+
await fs7.unlink(globalPidFile);
|
|
70654
71009
|
} catch {}
|
|
70655
71010
|
};
|
|
70656
71011
|
process.on("SIGINT", async () => {
|
|
@@ -70664,47 +71019,23 @@ async function startSyncScheduler2(name, options) {
|
|
|
70664
71019
|
console.log(source_default.cyan(`
|
|
70665
71020
|
=== Starting Sync Scheduler Service ===`));
|
|
70666
71021
|
console.log(source_default.cyan(`Target Server: ${serverUrl}`));
|
|
70667
|
-
|
|
71022
|
+
if (isGlobalScheduler) {
|
|
71023
|
+
console.log(source_default.cyan(`Mode: Global daemon (all enabled jobs, auto-discovers new jobs)`));
|
|
71024
|
+
console.log(source_default.cyan(`Currently registered: ${jobs.map((j) => j.name).join(", ")}
|
|
70668
71025
|
`));
|
|
71026
|
+
} else {
|
|
71027
|
+
console.log(source_default.cyan(`Active Profile: ${jobs.map((j) => j.name).join(", ")}
|
|
71028
|
+
`));
|
|
71029
|
+
}
|
|
70669
71030
|
const activeJobs = new Set;
|
|
70670
|
-
|
|
70671
|
-
|
|
70672
|
-
|
|
70673
|
-
|
|
70674
|
-
|
|
70675
|
-
console.
|
|
70676
|
-
return;
|
|
70677
|
-
}
|
|
70678
|
-
activeJobs.add(job.id);
|
|
70679
|
-
const engine = new SyncEngine(job, serverUrl, apiKey, stateDbPath);
|
|
70680
|
-
engine.on((event) => {
|
|
70681
|
-
const time = new Date().toLocaleTimeString();
|
|
70682
|
-
if (event.type === "start") {
|
|
70683
|
-
console.log(source_default.blue(`[${time}] [Sync: ${job.name}] Running scan...`));
|
|
70684
|
-
} else if (event.type === "scan-complete") {
|
|
70685
|
-
console.log(source_default.blue(`[${time}] [Sync: ${job.name}] Scan completed.`));
|
|
70686
|
-
} else if (event.type === "upload-complete") {
|
|
70687
|
-
console.log(source_default.green(`[${time}] [Sync: ${job.name}] ✔ Uploaded: ${event.path}`));
|
|
70688
|
-
} else if (event.type === "download-complete") {
|
|
70689
|
-
console.log(source_default.green(`[${time}] [Sync: ${job.name}] ✔ Downloaded: ${event.path}`));
|
|
70690
|
-
} else if (event.type === "conflict") {
|
|
70691
|
-
console.log(source_default.yellow(`[${time}] [Sync: ${job.name}] ⚠ Conflict resolved for: ${event.path}`));
|
|
70692
|
-
} else if (event.type === "error") {
|
|
70693
|
-
console.error(source_default.red(`[${time}] [Sync: ${job.name}] ✘ Error on "${event.path}": ${event.error?.message}`));
|
|
70694
|
-
} else if (event.type === "complete") {
|
|
70695
|
-
console.log(source_default.green(`[${time}] [Sync: ${job.name}] ✔ Sync pass completed successfully.`));
|
|
70696
|
-
}
|
|
71031
|
+
const scheduledJobs = new Map;
|
|
71032
|
+
await reconcileSchedules2(activeJobs, scheduledJobs, foregroundJobName);
|
|
71033
|
+
if (isGlobalScheduler) {
|
|
71034
|
+
setInterval(() => {
|
|
71035
|
+
reconcileSchedules2(activeJobs, scheduledJobs).catch((reconcileErr) => {
|
|
71036
|
+
console.error(source_default.red(`[Scheduler] Failed to refresh job list: ${reconcileErr.message}`));
|
|
70697
71037
|
});
|
|
70698
|
-
|
|
70699
|
-
await engine.run();
|
|
70700
|
-
} catch (runErr) {
|
|
70701
|
-
console.error(source_default.red(`[${new Date().toLocaleTimeString()}] [Sync: ${job.name}] Sync crash: ${runErr.message}`));
|
|
70702
|
-
} finally {
|
|
70703
|
-
activeJobs.delete(job.id);
|
|
70704
|
-
}
|
|
70705
|
-
};
|
|
70706
|
-
triggerSyncRun();
|
|
70707
|
-
setInterval(triggerSyncRun, intervalDelay);
|
|
71038
|
+
}, RECONCILE_INTERVAL_MS2);
|
|
70708
71039
|
}
|
|
70709
71040
|
} catch (err) {
|
|
70710
71041
|
console.error(source_default.red(`Scheduler crashed: ${err.message}`));
|
|
@@ -70713,40 +71044,33 @@ async function startSyncScheduler2(name, options) {
|
|
|
70713
71044
|
}
|
|
70714
71045
|
async function stopSyncScheduler2(name) {
|
|
70715
71046
|
try {
|
|
70716
|
-
const hsyncDir = path8.join(os5.homedir(), ".hsync");
|
|
70717
71047
|
if (name) {
|
|
70718
|
-
|
|
71048
|
+
console.log(source_default.yellow(`The sync daemon manages all jobs together. Stopping global daemon (includes "${name}")...`));
|
|
71049
|
+
}
|
|
71050
|
+
const globalFile = path8.join(getHsyncDir2(), "daemon.pid");
|
|
71051
|
+
const globalDaemon = await readPidRecord2(globalFile);
|
|
71052
|
+
if (globalDaemon) {
|
|
71053
|
+
process.kill(globalDaemon.pid, "SIGTERM");
|
|
70719
71054
|
try {
|
|
70720
|
-
|
|
70721
|
-
|
|
70722
|
-
|
|
70723
|
-
await fs7.unlink(pidFile);
|
|
70724
|
-
console.log(source_default.green(`✔ Sync daemon for "${name}" (PID: ${pid}) has been stopped.`));
|
|
70725
|
-
} catch {
|
|
70726
|
-
console.error(source_default.red(`Error: No active sync daemon found for "${name}".`));
|
|
70727
|
-
}
|
|
71055
|
+
await fs7.unlink(globalFile);
|
|
71056
|
+
} catch {}
|
|
71057
|
+
console.log(source_default.green(`✔ Global sync daemon stopped (PID: ${globalDaemon.pid}).`));
|
|
70728
71058
|
} else {
|
|
70729
|
-
|
|
70730
|
-
|
|
70731
|
-
|
|
70732
|
-
|
|
70733
|
-
|
|
70734
|
-
|
|
70735
|
-
|
|
70736
|
-
const pidFile = path8.join(hsyncDir, file);
|
|
70737
|
-
try {
|
|
70738
|
-
const pidText = await fs7.readFile(pidFile, "utf8");
|
|
70739
|
-
const pid = parseInt(pidText.trim(), 10);
|
|
70740
|
-
process.kill(pid, "SIGTERM");
|
|
70741
|
-
await fs7.unlink(pidFile);
|
|
70742
|
-
const jobName = file.replace("daemon-", "").replace(".pid", "");
|
|
70743
|
-
console.log(source_default.green(`✔ Stopped sync daemon (PID: ${pid}) for ${jobName === "daemon" ? "all jobs" : jobName}.`));
|
|
70744
|
-
} catch {
|
|
70745
|
-
try {
|
|
70746
|
-
await fs7.unlink(pidFile);
|
|
70747
|
-
} catch {}
|
|
70748
|
-
}
|
|
71059
|
+
console.log(source_default.yellow("Global sync daemon is not running."));
|
|
71060
|
+
console.log(source_default.gray("Check with: hsync sync status"));
|
|
71061
|
+
}
|
|
71062
|
+
const legacyDaemons = await listAllDaemonPids2();
|
|
71063
|
+
for (const daemon of legacyDaemons) {
|
|
71064
|
+
if (daemon.jobName === "all jobs") {
|
|
71065
|
+
continue;
|
|
70749
71066
|
}
|
|
71067
|
+
try {
|
|
71068
|
+
process.kill(daemon.pid, "SIGTERM");
|
|
71069
|
+
} catch {}
|
|
71070
|
+
try {
|
|
71071
|
+
await fs7.unlink(daemon.pidFile);
|
|
71072
|
+
} catch {}
|
|
71073
|
+
console.log(source_default.green(`✔ Stopped legacy per-job daemon (PID: ${daemon.pid}) for ${daemon.jobName}.`));
|
|
70750
71074
|
}
|
|
70751
71075
|
} catch (err) {
|
|
70752
71076
|
console.error(source_default.red(`Error stopping daemon: ${err.message}`));
|
|
@@ -70866,7 +71190,7 @@ async function editSyncJob2(name, options) {
|
|
|
70866
71190
|
}
|
|
70867
71191
|
|
|
70868
71192
|
// src/commands/ui.ts
|
|
70869
|
-
var __dirname = "/media/
|
|
71193
|
+
var __dirname = "/media/hyper2/HYPER/projects/node/hmd/file-sync-v2/packages/cli/src/commands";
|
|
70870
71194
|
function openBrowser(url2) {
|
|
70871
71195
|
const start = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
70872
71196
|
exec(`${start} ${url2}`).unref();
|
|
@@ -70914,29 +71238,22 @@ async function startUiServer(options) {
|
|
|
70914
71238
|
});
|
|
70915
71239
|
localRouter.get("/jobs/:name/status", async (req, res) => {
|
|
70916
71240
|
try {
|
|
70917
|
-
const
|
|
70918
|
-
const hsyncDir = path9.join(os6.homedir(), ".hsync");
|
|
70919
|
-
const pidFile = path9.join(hsyncDir, `daemon-${jobName}.pid`);
|
|
70920
|
-
const globalPidFile = path9.join(hsyncDir, "daemon.pid");
|
|
71241
|
+
const globalPidFile = path9.join(os6.homedir(), ".hsync", "daemon.pid");
|
|
70921
71242
|
let pidText = "";
|
|
70922
71243
|
try {
|
|
70923
|
-
pidText = await fs8.readFile(
|
|
71244
|
+
pidText = await fs8.readFile(globalPidFile, "utf8");
|
|
70924
71245
|
} catch {
|
|
70925
|
-
|
|
70926
|
-
pidText = await fs8.readFile(globalPidFile, "utf8");
|
|
70927
|
-
} catch {
|
|
70928
|
-
return res.json({ running: false });
|
|
70929
|
-
}
|
|
71246
|
+
return res.json({ running: false, mode: "global" });
|
|
70930
71247
|
}
|
|
70931
71248
|
const pid = parseInt(pidText.trim(), 10);
|
|
70932
71249
|
if (isNaN(pid)) {
|
|
70933
|
-
return res.json({ running: false });
|
|
71250
|
+
return res.json({ running: false, mode: "global" });
|
|
70934
71251
|
}
|
|
70935
71252
|
try {
|
|
70936
71253
|
process.kill(pid, 0);
|
|
70937
|
-
res.json({ running: true, pid });
|
|
71254
|
+
res.json({ running: true, pid, mode: "global" });
|
|
70938
71255
|
} catch {
|
|
70939
|
-
res.json({ running: false });
|
|
71256
|
+
res.json({ running: false, mode: "global" });
|
|
70940
71257
|
}
|
|
70941
71258
|
} catch (err) {
|
|
70942
71259
|
res.status(500).json({ error: err.message });
|
|
@@ -70944,18 +71261,16 @@ async function startUiServer(options) {
|
|
|
70944
71261
|
});
|
|
70945
71262
|
localRouter.post("/jobs/:name/start", async (req, res) => {
|
|
70946
71263
|
try {
|
|
70947
|
-
|
|
70948
|
-
|
|
70949
|
-
res.json({ success: true });
|
|
71264
|
+
await startSyncScheduler2(undefined, { daemon: true });
|
|
71265
|
+
res.json({ success: true, mode: "global" });
|
|
70950
71266
|
} catch (err) {
|
|
70951
71267
|
res.status(500).json({ error: err.message });
|
|
70952
71268
|
}
|
|
70953
71269
|
});
|
|
70954
71270
|
localRouter.post("/jobs/:name/stop", async (req, res) => {
|
|
70955
71271
|
try {
|
|
70956
|
-
|
|
70957
|
-
|
|
70958
|
-
res.json({ success: true });
|
|
71272
|
+
await stopSyncScheduler2();
|
|
71273
|
+
res.json({ success: true, mode: "global" });
|
|
70959
71274
|
} catch (err) {
|
|
70960
71275
|
res.status(500).json({ error: err.message });
|
|
70961
71276
|
}
|
|
@@ -71100,7 +71415,7 @@ Press Ctrl+C to stop the server...
|
|
|
71100
71415
|
// package.json
|
|
71101
71416
|
var package_default = {
|
|
71102
71417
|
name: "hsync-cli",
|
|
71103
|
-
version: "1.1.
|
|
71418
|
+
version: "1.1.2",
|
|
71104
71419
|
description: "hsync CLI for file sync operations",
|
|
71105
71420
|
bin: {
|
|
71106
71421
|
hsync: "./dist/bin.js"
|
|
@@ -71177,12 +71492,15 @@ syncCmd.command("list").description("List all registered local sync profiles").a
|
|
|
71177
71492
|
syncCmd.command("remove <name>").description("Remove a registered local sync job").action(async (name) => {
|
|
71178
71493
|
await removeSyncJob(name);
|
|
71179
71494
|
});
|
|
71180
|
-
syncCmd.command("start [name]").description("Start
|
|
71495
|
+
syncCmd.command("start [name]").description("Start sync scheduler (use --daemon to run all jobs in background)").option("-d, --daemon", "Run global sync daemon for all enabled jobs in the background").action(async (name, options) => {
|
|
71181
71496
|
await startSyncScheduler(name, options);
|
|
71182
71497
|
});
|
|
71183
|
-
syncCmd.command("stop [name]").description("Stop
|
|
71498
|
+
syncCmd.command("stop [name]").description("Stop the global background sync daemon").action(async (name) => {
|
|
71184
71499
|
await stopSyncScheduler(name);
|
|
71185
71500
|
});
|
|
71501
|
+
syncCmd.command("status").description("Show active background sync daemons").action(async () => {
|
|
71502
|
+
await showSyncStatus();
|
|
71503
|
+
});
|
|
71186
71504
|
syncCmd.command("autostart <action>").description('Configure hsync sync autostart daemon on login (action: "enable" | "disable")').action(async (action) => {
|
|
71187
71505
|
if (action === "enable") {
|
|
71188
71506
|
await configureAutostart(true);
|
package/dist/web/index.html
CHANGED
|
File without changes
|