@hasna/cloud 0.1.17 → 0.1.18
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/cli/index.js +174 -31
- package/dist/index.js +167 -24
- package/dist/sync-schedule.d.ts +7 -13
- package/dist/sync-schedule.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -11343,15 +11343,15 @@ __export(exports_discover, {
|
|
|
11343
11343
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
|
|
11344
11344
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
11345
11345
|
});
|
|
11346
|
-
import { readdirSync as readdirSync5, existsSync as
|
|
11346
|
+
import { readdirSync as readdirSync5, existsSync as existsSync8 } from "fs";
|
|
11347
11347
|
import { join as join8 } from "path";
|
|
11348
|
-
import { homedir as
|
|
11348
|
+
import { homedir as homedir7 } from "os";
|
|
11349
11349
|
function isSyncExcludedTable2(table) {
|
|
11350
11350
|
return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
|
|
11351
11351
|
}
|
|
11352
11352
|
function discoverServices2() {
|
|
11353
|
-
const dataDir = join8(
|
|
11354
|
-
if (!
|
|
11353
|
+
const dataDir = join8(homedir7(), ".hasna");
|
|
11354
|
+
if (!existsSync8(dataDir))
|
|
11355
11355
|
return [];
|
|
11356
11356
|
try {
|
|
11357
11357
|
const entries = readdirSync5(dataDir, { withFileTypes: true });
|
|
@@ -11372,8 +11372,8 @@ function discoverSyncableServices2() {
|
|
|
11372
11372
|
return local.filter((s) => pgSet.has(s));
|
|
11373
11373
|
}
|
|
11374
11374
|
function getServiceDbPath(service) {
|
|
11375
|
-
const dataDir = join8(
|
|
11376
|
-
if (!
|
|
11375
|
+
const dataDir = join8(homedir7(), ".hasna", service);
|
|
11376
|
+
if (!existsSync8(dataDir))
|
|
11377
11377
|
return null;
|
|
11378
11378
|
const candidates = [
|
|
11379
11379
|
join8(dataDir, `${service}.db`),
|
|
@@ -11389,7 +11389,7 @@ function getServiceDbPath(service) {
|
|
|
11389
11389
|
}
|
|
11390
11390
|
} catch {}
|
|
11391
11391
|
for (const p of candidates) {
|
|
11392
|
-
if (
|
|
11392
|
+
if (existsSync8(p))
|
|
11393
11393
|
return p;
|
|
11394
11394
|
}
|
|
11395
11395
|
return null;
|
|
@@ -12131,18 +12131,10 @@ class PgAdapterAsync2 {
|
|
|
12131
12131
|
// src/sync-schedule.ts
|
|
12132
12132
|
init_config();
|
|
12133
12133
|
import { join as join5, dirname } from "path";
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
12138
|
-
const jsPath = join5(dir, "scheduled-sync.js");
|
|
12139
|
-
try {
|
|
12140
|
-
const { existsSync: existsSync5 } = __require("fs");
|
|
12141
|
-
if (existsSync5(tsPath))
|
|
12142
|
-
return tsPath;
|
|
12143
|
-
} catch {}
|
|
12144
|
-
return jsPath;
|
|
12145
|
-
}
|
|
12134
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync5 } from "fs";
|
|
12135
|
+
import { homedir as homedir5, platform } from "os";
|
|
12136
|
+
var SERVICE_NAME = "hasna-cloud-sync";
|
|
12137
|
+
var CONFIG_DIR3 = join5(homedir5(), ".hasna", "cloud");
|
|
12146
12138
|
function parseInterval(input) {
|
|
12147
12139
|
const trimmed = input.trim().toLowerCase();
|
|
12148
12140
|
const hourMatch = trimmed.match(/^(\d+)\s*h$/);
|
|
@@ -12181,19 +12173,161 @@ function minutesToCron(minutes) {
|
|
|
12181
12173
|
}
|
|
12182
12174
|
return `*/${minutes} * * * *`;
|
|
12183
12175
|
}
|
|
12176
|
+
function getWorkerPath() {
|
|
12177
|
+
const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
|
|
12178
|
+
const tsPath = join5(dir, "scheduled-sync.ts");
|
|
12179
|
+
const jsPath = join5(dir, "scheduled-sync.js");
|
|
12180
|
+
try {
|
|
12181
|
+
if (existsSync5(tsPath))
|
|
12182
|
+
return tsPath;
|
|
12183
|
+
} catch {}
|
|
12184
|
+
return jsPath;
|
|
12185
|
+
}
|
|
12186
|
+
function getBunPath() {
|
|
12187
|
+
const candidates = [
|
|
12188
|
+
join5(homedir5(), ".bun", "bin", "bun"),
|
|
12189
|
+
"/usr/local/bin/bun",
|
|
12190
|
+
"/usr/bin/bun"
|
|
12191
|
+
];
|
|
12192
|
+
for (const p of candidates) {
|
|
12193
|
+
if (existsSync5(p))
|
|
12194
|
+
return p;
|
|
12195
|
+
}
|
|
12196
|
+
return "bun";
|
|
12197
|
+
}
|
|
12198
|
+
function getLaunchdPlistPath() {
|
|
12199
|
+
return join5(homedir5(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
|
|
12200
|
+
}
|
|
12201
|
+
function createLaunchdPlist(intervalMinutes) {
|
|
12202
|
+
const workerPath = getWorkerPath();
|
|
12203
|
+
const bunPath = getBunPath();
|
|
12204
|
+
const logPath = join5(CONFIG_DIR3, "sync.log");
|
|
12205
|
+
const errorLogPath = join5(CONFIG_DIR3, "sync-error.log");
|
|
12206
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
12207
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
12208
|
+
<plist version="1.0">
|
|
12209
|
+
<dict>
|
|
12210
|
+
<key>Label</key>
|
|
12211
|
+
<string>com.hasna.cloud-sync</string>
|
|
12212
|
+
<key>ProgramArguments</key>
|
|
12213
|
+
<array>
|
|
12214
|
+
<string>${bunPath}</string>
|
|
12215
|
+
<string>run</string>
|
|
12216
|
+
<string>${workerPath}</string>
|
|
12217
|
+
</array>
|
|
12218
|
+
<key>StartInterval</key>
|
|
12219
|
+
<integer>${intervalMinutes * 60}</integer>
|
|
12220
|
+
<key>RunAtLoad</key>
|
|
12221
|
+
<true/>
|
|
12222
|
+
<key>StandardOutPath</key>
|
|
12223
|
+
<string>${logPath}</string>
|
|
12224
|
+
<key>StandardErrorPath</key>
|
|
12225
|
+
<string>${errorLogPath}</string>
|
|
12226
|
+
<key>EnvironmentVariables</key>
|
|
12227
|
+
<dict>
|
|
12228
|
+
<key>PATH</key>
|
|
12229
|
+
<string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
|
|
12230
|
+
<key>HOME</key>
|
|
12231
|
+
<string>${homedir5()}</string>
|
|
12232
|
+
</dict>
|
|
12233
|
+
</dict>
|
|
12234
|
+
</plist>`;
|
|
12235
|
+
}
|
|
12236
|
+
async function registerLaunchd(intervalMinutes) {
|
|
12237
|
+
const plistPath = getLaunchdPlistPath();
|
|
12238
|
+
const plistDir = dirname(plistPath);
|
|
12239
|
+
mkdirSync5(plistDir, { recursive: true });
|
|
12240
|
+
try {
|
|
12241
|
+
await Bun.spawn(["launchctl", "unload", plistPath]).exited;
|
|
12242
|
+
} catch {}
|
|
12243
|
+
writeFileSync3(plistPath, createLaunchdPlist(intervalMinutes));
|
|
12244
|
+
await Bun.spawn(["launchctl", "load", plistPath]).exited;
|
|
12245
|
+
}
|
|
12246
|
+
async function removeLaunchd() {
|
|
12247
|
+
const plistPath = getLaunchdPlistPath();
|
|
12248
|
+
try {
|
|
12249
|
+
await Bun.spawn(["launchctl", "unload", plistPath]).exited;
|
|
12250
|
+
} catch {}
|
|
12251
|
+
try {
|
|
12252
|
+
unlinkSync(plistPath);
|
|
12253
|
+
} catch {}
|
|
12254
|
+
}
|
|
12255
|
+
function getSystemdDir() {
|
|
12256
|
+
return join5(homedir5(), ".config", "systemd", "user");
|
|
12257
|
+
}
|
|
12258
|
+
function createSystemdService() {
|
|
12259
|
+
const workerPath = getWorkerPath();
|
|
12260
|
+
const bunPath = getBunPath();
|
|
12261
|
+
return `[Unit]
|
|
12262
|
+
Description=Hasna Cloud Sync
|
|
12263
|
+
After=network.target
|
|
12264
|
+
|
|
12265
|
+
[Service]
|
|
12266
|
+
Type=oneshot
|
|
12267
|
+
ExecStart=${bunPath} run ${workerPath}
|
|
12268
|
+
Environment=HOME=${homedir5()}
|
|
12269
|
+
Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
|
|
12270
|
+
|
|
12271
|
+
[Install]
|
|
12272
|
+
WantedBy=default.target
|
|
12273
|
+
`;
|
|
12274
|
+
}
|
|
12275
|
+
function createSystemdTimer(intervalMinutes) {
|
|
12276
|
+
return `[Unit]
|
|
12277
|
+
Description=Hasna Cloud Sync Timer
|
|
12278
|
+
|
|
12279
|
+
[Timer]
|
|
12280
|
+
OnBootSec=${intervalMinutes}min
|
|
12281
|
+
OnUnitActiveSec=${intervalMinutes}min
|
|
12282
|
+
Persistent=true
|
|
12283
|
+
|
|
12284
|
+
[Install]
|
|
12285
|
+
WantedBy=timers.target
|
|
12286
|
+
`;
|
|
12287
|
+
}
|
|
12288
|
+
async function registerSystemd(intervalMinutes) {
|
|
12289
|
+
const dir = getSystemdDir();
|
|
12290
|
+
mkdirSync5(dir, { recursive: true });
|
|
12291
|
+
writeFileSync3(join5(dir, `${SERVICE_NAME}.service`), createSystemdService());
|
|
12292
|
+
writeFileSync3(join5(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
|
|
12293
|
+
await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
|
|
12294
|
+
await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
|
|
12295
|
+
}
|
|
12296
|
+
async function removeSystemd() {
|
|
12297
|
+
try {
|
|
12298
|
+
await Bun.spawn(["systemctl", "--user", "disable", "--now", `${SERVICE_NAME}.timer`]).exited;
|
|
12299
|
+
} catch {}
|
|
12300
|
+
const dir = getSystemdDir();
|
|
12301
|
+
try {
|
|
12302
|
+
unlinkSync(join5(dir, `${SERVICE_NAME}.service`));
|
|
12303
|
+
} catch {}
|
|
12304
|
+
try {
|
|
12305
|
+
unlinkSync(join5(dir, `${SERVICE_NAME}.timer`));
|
|
12306
|
+
} catch {}
|
|
12307
|
+
try {
|
|
12308
|
+
await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
|
|
12309
|
+
} catch {}
|
|
12310
|
+
}
|
|
12184
12311
|
async function registerSyncSchedule(intervalMinutes) {
|
|
12185
12312
|
if (intervalMinutes <= 0) {
|
|
12186
12313
|
throw new Error("Interval must be a positive number of minutes.");
|
|
12187
12314
|
}
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12315
|
+
mkdirSync5(CONFIG_DIR3, { recursive: true });
|
|
12316
|
+
if (platform() === "darwin") {
|
|
12317
|
+
await registerLaunchd(intervalMinutes);
|
|
12318
|
+
} else {
|
|
12319
|
+
await registerSystemd(intervalMinutes);
|
|
12320
|
+
}
|
|
12191
12321
|
const config = getCloudConfig2();
|
|
12192
12322
|
config.sync.schedule_minutes = intervalMinutes;
|
|
12193
12323
|
saveCloudConfig2(config);
|
|
12194
12324
|
}
|
|
12195
12325
|
async function removeSyncSchedule() {
|
|
12196
|
-
|
|
12326
|
+
if (platform() === "darwin") {
|
|
12327
|
+
await removeLaunchd();
|
|
12328
|
+
} else {
|
|
12329
|
+
await removeSystemd();
|
|
12330
|
+
}
|
|
12197
12331
|
const config = getCloudConfig2();
|
|
12198
12332
|
config.sync.schedule_minutes = 0;
|
|
12199
12333
|
saveCloudConfig2(config);
|
|
@@ -12202,17 +12336,26 @@ function getSyncScheduleStatus() {
|
|
|
12202
12336
|
const config = getCloudConfig2();
|
|
12203
12337
|
const minutes = config.sync.schedule_minutes;
|
|
12204
12338
|
const registered = minutes > 0;
|
|
12339
|
+
let mechanism = "none";
|
|
12340
|
+
if (registered) {
|
|
12341
|
+
if (platform() === "darwin") {
|
|
12342
|
+
mechanism = existsSync5(getLaunchdPlistPath()) ? "launchd" : "none";
|
|
12343
|
+
} else {
|
|
12344
|
+
mechanism = existsSync5(join5(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12205
12347
|
return {
|
|
12206
12348
|
registered,
|
|
12207
12349
|
schedule_minutes: minutes,
|
|
12208
|
-
cron_expression: registered ? minutesToCron(minutes) : null
|
|
12350
|
+
cron_expression: registered ? minutesToCron(minutes) : null,
|
|
12351
|
+
mechanism
|
|
12209
12352
|
};
|
|
12210
12353
|
}
|
|
12211
12354
|
|
|
12212
12355
|
// src/scheduled-sync.ts
|
|
12213
12356
|
init_config();
|
|
12214
12357
|
init_adapter();
|
|
12215
|
-
import { existsSync as
|
|
12358
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
12216
12359
|
import { join as join6 } from "path";
|
|
12217
12360
|
|
|
12218
12361
|
// src/sync-incremental.ts
|
|
@@ -12355,7 +12498,7 @@ function discoverSyncableServices() {
|
|
|
12355
12498
|
if (!entry.isDirectory())
|
|
12356
12499
|
continue;
|
|
12357
12500
|
const dbPath = join6(hasnaDir, entry.name, `${entry.name}.db`);
|
|
12358
|
-
if (
|
|
12501
|
+
if (existsSync6(dbPath)) {
|
|
12359
12502
|
services.push(entry.name);
|
|
12360
12503
|
}
|
|
12361
12504
|
}
|
|
@@ -12378,7 +12521,7 @@ async function runScheduledSync() {
|
|
|
12378
12521
|
};
|
|
12379
12522
|
try {
|
|
12380
12523
|
const dbPath = join6(getDataDir(service), `${service}.db`);
|
|
12381
|
-
if (!
|
|
12524
|
+
if (!existsSync6(dbPath)) {
|
|
12382
12525
|
continue;
|
|
12383
12526
|
}
|
|
12384
12527
|
const local = new SqliteAdapter(dbPath);
|
|
@@ -12421,9 +12564,9 @@ async function runScheduledSync() {
|
|
|
12421
12564
|
}
|
|
12422
12565
|
|
|
12423
12566
|
// src/discover.ts
|
|
12424
|
-
import { readdirSync as readdirSync4, existsSync as
|
|
12567
|
+
import { readdirSync as readdirSync4, existsSync as existsSync7 } from "fs";
|
|
12425
12568
|
import { join as join7 } from "path";
|
|
12426
|
-
import { homedir as
|
|
12569
|
+
import { homedir as homedir6 } from "os";
|
|
12427
12570
|
var SYNC_EXCLUDED_TABLE_PATTERNS = [
|
|
12428
12571
|
/^sqlite_/,
|
|
12429
12572
|
/_fts$/,
|
|
@@ -12435,8 +12578,8 @@ function isSyncExcludedTable(table) {
|
|
|
12435
12578
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
12436
12579
|
}
|
|
12437
12580
|
function discoverServices() {
|
|
12438
|
-
const dataDir = join7(
|
|
12439
|
-
if (!
|
|
12581
|
+
const dataDir = join7(homedir6(), ".hasna");
|
|
12582
|
+
if (!existsSync7(dataDir))
|
|
12440
12583
|
return [];
|
|
12441
12584
|
try {
|
|
12442
12585
|
const entries = readdirSync4(dataDir, { withFileTypes: true });
|
package/dist/index.js
CHANGED
|
@@ -9347,15 +9347,15 @@ __export(exports_discover, {
|
|
|
9347
9347
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
9348
9348
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
9349
9349
|
});
|
|
9350
|
-
import { readdirSync as readdirSync3, existsSync as
|
|
9350
|
+
import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
|
|
9351
9351
|
import { join as join6 } from "path";
|
|
9352
|
-
import { homedir as
|
|
9352
|
+
import { homedir as homedir5 } from "os";
|
|
9353
9353
|
function isSyncExcludedTable(table) {
|
|
9354
9354
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
9355
9355
|
}
|
|
9356
9356
|
function discoverServices() {
|
|
9357
|
-
const dataDir = join6(
|
|
9358
|
-
if (!
|
|
9357
|
+
const dataDir = join6(homedir5(), ".hasna");
|
|
9358
|
+
if (!existsSync6(dataDir))
|
|
9359
9359
|
return [];
|
|
9360
9360
|
try {
|
|
9361
9361
|
const entries = readdirSync3(dataDir, { withFileTypes: true });
|
|
@@ -9376,8 +9376,8 @@ function discoverSyncableServices2() {
|
|
|
9376
9376
|
return local.filter((s) => pgSet.has(s));
|
|
9377
9377
|
}
|
|
9378
9378
|
function getServiceDbPath(service) {
|
|
9379
|
-
const dataDir = join6(
|
|
9380
|
-
if (!
|
|
9379
|
+
const dataDir = join6(homedir5(), ".hasna", service);
|
|
9380
|
+
if (!existsSync6(dataDir))
|
|
9381
9381
|
return null;
|
|
9382
9382
|
const candidates = [
|
|
9383
9383
|
join6(dataDir, `${service}.db`),
|
|
@@ -9393,7 +9393,7 @@ function getServiceDbPath(service) {
|
|
|
9393
9393
|
}
|
|
9394
9394
|
} catch {}
|
|
9395
9395
|
for (const p of candidates) {
|
|
9396
|
-
if (
|
|
9396
|
+
if (existsSync6(p))
|
|
9397
9397
|
return p;
|
|
9398
9398
|
}
|
|
9399
9399
|
return null;
|
|
@@ -10536,18 +10536,10 @@ async function runScheduledSync() {
|
|
|
10536
10536
|
// src/sync-schedule.ts
|
|
10537
10537
|
init_config();
|
|
10538
10538
|
import { join as join5, dirname } from "path";
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
const jsPath = join5(dir, "scheduled-sync.js");
|
|
10544
|
-
try {
|
|
10545
|
-
const { existsSync: existsSync5 } = __require("fs");
|
|
10546
|
-
if (existsSync5(tsPath))
|
|
10547
|
-
return tsPath;
|
|
10548
|
-
} catch {}
|
|
10549
|
-
return jsPath;
|
|
10550
|
-
}
|
|
10539
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
10540
|
+
import { homedir as homedir4, platform } from "os";
|
|
10541
|
+
var SERVICE_NAME = "hasna-cloud-sync";
|
|
10542
|
+
var CONFIG_DIR2 = join5(homedir4(), ".hasna", "cloud");
|
|
10551
10543
|
function parseInterval(input) {
|
|
10552
10544
|
const trimmed = input.trim().toLowerCase();
|
|
10553
10545
|
const hourMatch = trimmed.match(/^(\d+)\s*h$/);
|
|
@@ -10586,19 +10578,161 @@ function minutesToCron(minutes) {
|
|
|
10586
10578
|
}
|
|
10587
10579
|
return `*/${minutes} * * * *`;
|
|
10588
10580
|
}
|
|
10581
|
+
function getWorkerPath() {
|
|
10582
|
+
const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
|
|
10583
|
+
const tsPath = join5(dir, "scheduled-sync.ts");
|
|
10584
|
+
const jsPath = join5(dir, "scheduled-sync.js");
|
|
10585
|
+
try {
|
|
10586
|
+
if (existsSync5(tsPath))
|
|
10587
|
+
return tsPath;
|
|
10588
|
+
} catch {}
|
|
10589
|
+
return jsPath;
|
|
10590
|
+
}
|
|
10591
|
+
function getBunPath() {
|
|
10592
|
+
const candidates = [
|
|
10593
|
+
join5(homedir4(), ".bun", "bin", "bun"),
|
|
10594
|
+
"/usr/local/bin/bun",
|
|
10595
|
+
"/usr/bin/bun"
|
|
10596
|
+
];
|
|
10597
|
+
for (const p of candidates) {
|
|
10598
|
+
if (existsSync5(p))
|
|
10599
|
+
return p;
|
|
10600
|
+
}
|
|
10601
|
+
return "bun";
|
|
10602
|
+
}
|
|
10603
|
+
function getLaunchdPlistPath() {
|
|
10604
|
+
return join5(homedir4(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
|
|
10605
|
+
}
|
|
10606
|
+
function createLaunchdPlist(intervalMinutes) {
|
|
10607
|
+
const workerPath = getWorkerPath();
|
|
10608
|
+
const bunPath = getBunPath();
|
|
10609
|
+
const logPath = join5(CONFIG_DIR2, "sync.log");
|
|
10610
|
+
const errorLogPath = join5(CONFIG_DIR2, "sync-error.log");
|
|
10611
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
10612
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
10613
|
+
<plist version="1.0">
|
|
10614
|
+
<dict>
|
|
10615
|
+
<key>Label</key>
|
|
10616
|
+
<string>com.hasna.cloud-sync</string>
|
|
10617
|
+
<key>ProgramArguments</key>
|
|
10618
|
+
<array>
|
|
10619
|
+
<string>${bunPath}</string>
|
|
10620
|
+
<string>run</string>
|
|
10621
|
+
<string>${workerPath}</string>
|
|
10622
|
+
</array>
|
|
10623
|
+
<key>StartInterval</key>
|
|
10624
|
+
<integer>${intervalMinutes * 60}</integer>
|
|
10625
|
+
<key>RunAtLoad</key>
|
|
10626
|
+
<true/>
|
|
10627
|
+
<key>StandardOutPath</key>
|
|
10628
|
+
<string>${logPath}</string>
|
|
10629
|
+
<key>StandardErrorPath</key>
|
|
10630
|
+
<string>${errorLogPath}</string>
|
|
10631
|
+
<key>EnvironmentVariables</key>
|
|
10632
|
+
<dict>
|
|
10633
|
+
<key>PATH</key>
|
|
10634
|
+
<string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
|
|
10635
|
+
<key>HOME</key>
|
|
10636
|
+
<string>${homedir4()}</string>
|
|
10637
|
+
</dict>
|
|
10638
|
+
</dict>
|
|
10639
|
+
</plist>`;
|
|
10640
|
+
}
|
|
10641
|
+
async function registerLaunchd(intervalMinutes) {
|
|
10642
|
+
const plistPath = getLaunchdPlistPath();
|
|
10643
|
+
const plistDir = dirname(plistPath);
|
|
10644
|
+
mkdirSync3(plistDir, { recursive: true });
|
|
10645
|
+
try {
|
|
10646
|
+
await Bun.spawn(["launchctl", "unload", plistPath]).exited;
|
|
10647
|
+
} catch {}
|
|
10648
|
+
writeFileSync2(plistPath, createLaunchdPlist(intervalMinutes));
|
|
10649
|
+
await Bun.spawn(["launchctl", "load", plistPath]).exited;
|
|
10650
|
+
}
|
|
10651
|
+
async function removeLaunchd() {
|
|
10652
|
+
const plistPath = getLaunchdPlistPath();
|
|
10653
|
+
try {
|
|
10654
|
+
await Bun.spawn(["launchctl", "unload", plistPath]).exited;
|
|
10655
|
+
} catch {}
|
|
10656
|
+
try {
|
|
10657
|
+
unlinkSync(plistPath);
|
|
10658
|
+
} catch {}
|
|
10659
|
+
}
|
|
10660
|
+
function getSystemdDir() {
|
|
10661
|
+
return join5(homedir4(), ".config", "systemd", "user");
|
|
10662
|
+
}
|
|
10663
|
+
function createSystemdService() {
|
|
10664
|
+
const workerPath = getWorkerPath();
|
|
10665
|
+
const bunPath = getBunPath();
|
|
10666
|
+
return `[Unit]
|
|
10667
|
+
Description=Hasna Cloud Sync
|
|
10668
|
+
After=network.target
|
|
10669
|
+
|
|
10670
|
+
[Service]
|
|
10671
|
+
Type=oneshot
|
|
10672
|
+
ExecStart=${bunPath} run ${workerPath}
|
|
10673
|
+
Environment=HOME=${homedir4()}
|
|
10674
|
+
Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
|
|
10675
|
+
|
|
10676
|
+
[Install]
|
|
10677
|
+
WantedBy=default.target
|
|
10678
|
+
`;
|
|
10679
|
+
}
|
|
10680
|
+
function createSystemdTimer(intervalMinutes) {
|
|
10681
|
+
return `[Unit]
|
|
10682
|
+
Description=Hasna Cloud Sync Timer
|
|
10683
|
+
|
|
10684
|
+
[Timer]
|
|
10685
|
+
OnBootSec=${intervalMinutes}min
|
|
10686
|
+
OnUnitActiveSec=${intervalMinutes}min
|
|
10687
|
+
Persistent=true
|
|
10688
|
+
|
|
10689
|
+
[Install]
|
|
10690
|
+
WantedBy=timers.target
|
|
10691
|
+
`;
|
|
10692
|
+
}
|
|
10693
|
+
async function registerSystemd(intervalMinutes) {
|
|
10694
|
+
const dir = getSystemdDir();
|
|
10695
|
+
mkdirSync3(dir, { recursive: true });
|
|
10696
|
+
writeFileSync2(join5(dir, `${SERVICE_NAME}.service`), createSystemdService());
|
|
10697
|
+
writeFileSync2(join5(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
|
|
10698
|
+
await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
|
|
10699
|
+
await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
|
|
10700
|
+
}
|
|
10701
|
+
async function removeSystemd() {
|
|
10702
|
+
try {
|
|
10703
|
+
await Bun.spawn(["systemctl", "--user", "disable", "--now", `${SERVICE_NAME}.timer`]).exited;
|
|
10704
|
+
} catch {}
|
|
10705
|
+
const dir = getSystemdDir();
|
|
10706
|
+
try {
|
|
10707
|
+
unlinkSync(join5(dir, `${SERVICE_NAME}.service`));
|
|
10708
|
+
} catch {}
|
|
10709
|
+
try {
|
|
10710
|
+
unlinkSync(join5(dir, `${SERVICE_NAME}.timer`));
|
|
10711
|
+
} catch {}
|
|
10712
|
+
try {
|
|
10713
|
+
await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
|
|
10714
|
+
} catch {}
|
|
10715
|
+
}
|
|
10589
10716
|
async function registerSyncSchedule(intervalMinutes) {
|
|
10590
10717
|
if (intervalMinutes <= 0) {
|
|
10591
10718
|
throw new Error("Interval must be a positive number of minutes.");
|
|
10592
10719
|
}
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10720
|
+
mkdirSync3(CONFIG_DIR2, { recursive: true });
|
|
10721
|
+
if (platform() === "darwin") {
|
|
10722
|
+
await registerLaunchd(intervalMinutes);
|
|
10723
|
+
} else {
|
|
10724
|
+
await registerSystemd(intervalMinutes);
|
|
10725
|
+
}
|
|
10596
10726
|
const config = getCloudConfig();
|
|
10597
10727
|
config.sync.schedule_minutes = intervalMinutes;
|
|
10598
10728
|
saveCloudConfig(config);
|
|
10599
10729
|
}
|
|
10600
10730
|
async function removeSyncSchedule() {
|
|
10601
|
-
|
|
10731
|
+
if (platform() === "darwin") {
|
|
10732
|
+
await removeLaunchd();
|
|
10733
|
+
} else {
|
|
10734
|
+
await removeSystemd();
|
|
10735
|
+
}
|
|
10602
10736
|
const config = getCloudConfig();
|
|
10603
10737
|
config.sync.schedule_minutes = 0;
|
|
10604
10738
|
saveCloudConfig(config);
|
|
@@ -10607,10 +10741,19 @@ function getSyncScheduleStatus() {
|
|
|
10607
10741
|
const config = getCloudConfig();
|
|
10608
10742
|
const minutes = config.sync.schedule_minutes;
|
|
10609
10743
|
const registered = minutes > 0;
|
|
10744
|
+
let mechanism = "none";
|
|
10745
|
+
if (registered) {
|
|
10746
|
+
if (platform() === "darwin") {
|
|
10747
|
+
mechanism = existsSync5(getLaunchdPlistPath()) ? "launchd" : "none";
|
|
10748
|
+
} else {
|
|
10749
|
+
mechanism = existsSync5(join5(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
|
|
10750
|
+
}
|
|
10751
|
+
}
|
|
10610
10752
|
return {
|
|
10611
10753
|
registered,
|
|
10612
10754
|
schedule_minutes: minutes,
|
|
10613
|
-
cron_expression: registered ? minutesToCron(minutes) : null
|
|
10755
|
+
cron_expression: registered ? minutesToCron(minutes) : null,
|
|
10756
|
+
mechanism
|
|
10614
10757
|
};
|
|
10615
10758
|
}
|
|
10616
10759
|
// src/pg-migrate.ts
|
package/dist/sync-schedule.d.ts
CHANGED
|
@@ -9,34 +9,28 @@
|
|
|
9
9
|
export declare function parseInterval(input: string): number;
|
|
10
10
|
/**
|
|
11
11
|
* Convert minutes to a cron expression.
|
|
12
|
-
*
|
|
13
|
-
* - For intervals that divide evenly into 60: `*\/<n> * * * *`
|
|
14
|
-
* - For hourly multiples: `0 *\/<h> * * *`
|
|
15
|
-
* - Otherwise: `*\/<n> * * * *` (best-effort)
|
|
16
12
|
*/
|
|
17
13
|
export declare function minutesToCron(minutes: number): string;
|
|
18
14
|
export interface SyncScheduleStatus {
|
|
19
15
|
registered: boolean;
|
|
20
16
|
schedule_minutes: number;
|
|
21
17
|
cron_expression: string | null;
|
|
18
|
+
mechanism: "launchd" | "systemd" | "none";
|
|
22
19
|
}
|
|
23
20
|
/**
|
|
24
|
-
* Register a
|
|
25
|
-
* interval.
|
|
21
|
+
* Register a system-level scheduled sync.
|
|
26
22
|
*
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
23
|
+
* - macOS: creates a launchd plist in ~/Library/LaunchAgents/
|
|
24
|
+
* - Linux: creates a systemd user timer in ~/.config/systemd/user/
|
|
25
|
+
* - Persists interval in ~/.hasna/cloud/config.json
|
|
29
26
|
*/
|
|
30
27
|
export declare function registerSyncSchedule(intervalMinutes: number): Promise<void>;
|
|
31
28
|
/**
|
|
32
|
-
* Remove the registered sync
|
|
33
|
-
*
|
|
34
|
-
* - Calls `Bun.cron.remove()` to unregister the OS-level job.
|
|
35
|
-
* - Sets `schedule_minutes` to 0 in config.
|
|
29
|
+
* Remove the registered sync schedule.
|
|
36
30
|
*/
|
|
37
31
|
export declare function removeSyncSchedule(): Promise<void>;
|
|
38
32
|
/**
|
|
39
|
-
* Get the current sync schedule status
|
|
33
|
+
* Get the current sync schedule status.
|
|
40
34
|
*/
|
|
41
35
|
export declare function getSyncScheduleStatus(): SyncScheduleStatus;
|
|
42
36
|
//# sourceMappingURL=sync-schedule.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-schedule.d.ts","sourceRoot":"","sources":["../src/sync-schedule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync-schedule.d.ts","sourceRoot":"","sources":["../src/sync-schedule.ts"],"names":[],"mappings":"AAgBA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiCnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiBrD;AAsKD,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,CAoB1D"}
|
package/package.json
CHANGED