@hasna/cloud 0.1.16 → 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/adapter.d.ts.map +1 -1
- package/dist/cli/index.js +190 -38
- package/dist/config.d.ts +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +176 -28
- package/dist/mcp/index.js +10 -5
- package/dist/scheduled-sync.js +7 -3
- package/dist/sync-schedule.d.ts +7 -13
- package/dist/sync-schedule.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,MAAM,IAAI,CAAC;AAOpB,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC;IACjC,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IAC3B,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAC7B,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC;IAC9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAC1C,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACxC,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;CAChC;AAMD,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,CAAC,EAAE,CAAW;gBAET,IAAI,EAAE,MAAM;IAMxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,SAAS;IAS7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG;IAKvC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE;IAKzC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIvB,4FAA4F;IAC5F,KAAK,CAAC,GAAG,EAAE,MAAM;IAIjB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB;IAmBvC,KAAK,IAAI,IAAI;IAIb,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAK9B,oEAAoE;IACpE,IAAI,GAAG,IAAI,QAAQ,CAElB;CACF;AAMD,qBAAa,SAAU,YAAW,SAAS;IACzC,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,OAAO,CAA8B;gBAEjC,gBAAgB,EAAE,MAAM;gBACxB,IAAI,EAAE,EAAE,CAAC,IAAI;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -11039,7 +11039,8 @@ class PgAdapter {
|
|
|
11039
11039
|
_client = null;
|
|
11040
11040
|
constructor(arg) {
|
|
11041
11041
|
if (typeof arg === "string") {
|
|
11042
|
-
|
|
11042
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
11043
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
11043
11044
|
} else {
|
|
11044
11045
|
this.pool = arg;
|
|
11045
11046
|
}
|
|
@@ -11166,7 +11167,8 @@ class PgAdapterAsync {
|
|
|
11166
11167
|
pool;
|
|
11167
11168
|
constructor(arg) {
|
|
11168
11169
|
if (typeof arg === "string") {
|
|
11169
|
-
|
|
11170
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
11171
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
11170
11172
|
} else {
|
|
11171
11173
|
this.pool = arg;
|
|
11172
11174
|
}
|
|
@@ -11290,7 +11292,10 @@ function getConnectionString2(dbName) {
|
|
|
11290
11292
|
if (!host || !username) {
|
|
11291
11293
|
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
11292
11294
|
}
|
|
11293
|
-
const password = process.env[password_env]
|
|
11295
|
+
const password = process.env[password_env];
|
|
11296
|
+
if (password === undefined || password === "") {
|
|
11297
|
+
throw new Error(`RDS password not set. Export ${password_env} in your shell or add it to ~/.secrets/hasna/rds/live.env`);
|
|
11298
|
+
}
|
|
11294
11299
|
const sslParam = ssl ? "?sslmode=require" : "";
|
|
11295
11300
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
11296
11301
|
}
|
|
@@ -11317,7 +11322,7 @@ var init_config = __esm(() => {
|
|
|
11317
11322
|
password_env: exports_external.string().default("HASNA_RDS_PASSWORD"),
|
|
11318
11323
|
ssl: exports_external.boolean().default(true)
|
|
11319
11324
|
}).default({}),
|
|
11320
|
-
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("
|
|
11325
|
+
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
11321
11326
|
auto_sync_interval_minutes: exports_external.number().default(0),
|
|
11322
11327
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
11323
11328
|
sync: exports_external.object({
|
|
@@ -11338,15 +11343,15 @@ __export(exports_discover, {
|
|
|
11338
11343
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
|
|
11339
11344
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
11340
11345
|
});
|
|
11341
|
-
import { readdirSync as readdirSync5, existsSync as
|
|
11346
|
+
import { readdirSync as readdirSync5, existsSync as existsSync8 } from "fs";
|
|
11342
11347
|
import { join as join8 } from "path";
|
|
11343
|
-
import { homedir as
|
|
11348
|
+
import { homedir as homedir7 } from "os";
|
|
11344
11349
|
function isSyncExcludedTable2(table) {
|
|
11345
11350
|
return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
|
|
11346
11351
|
}
|
|
11347
11352
|
function discoverServices2() {
|
|
11348
|
-
const dataDir = join8(
|
|
11349
|
-
if (!
|
|
11353
|
+
const dataDir = join8(homedir7(), ".hasna");
|
|
11354
|
+
if (!existsSync8(dataDir))
|
|
11350
11355
|
return [];
|
|
11351
11356
|
try {
|
|
11352
11357
|
const entries = readdirSync5(dataDir, { withFileTypes: true });
|
|
@@ -11367,8 +11372,8 @@ function discoverSyncableServices2() {
|
|
|
11367
11372
|
return local.filter((s) => pgSet.has(s));
|
|
11368
11373
|
}
|
|
11369
11374
|
function getServiceDbPath(service) {
|
|
11370
|
-
const dataDir = join8(
|
|
11371
|
-
if (!
|
|
11375
|
+
const dataDir = join8(homedir7(), ".hasna", service);
|
|
11376
|
+
if (!existsSync8(dataDir))
|
|
11372
11377
|
return null;
|
|
11373
11378
|
const candidates = [
|
|
11374
11379
|
join8(dataDir, `${service}.db`),
|
|
@@ -11384,7 +11389,7 @@ function getServiceDbPath(service) {
|
|
|
11384
11389
|
}
|
|
11385
11390
|
} catch {}
|
|
11386
11391
|
for (const p of candidates) {
|
|
11387
|
-
if (
|
|
11392
|
+
if (existsSync8(p))
|
|
11388
11393
|
return p;
|
|
11389
11394
|
}
|
|
11390
11395
|
return null;
|
|
@@ -11468,7 +11473,7 @@ var CloudConfigSchema = exports_external.object({
|
|
|
11468
11473
|
password_env: exports_external.string().default("HASNA_RDS_PASSWORD"),
|
|
11469
11474
|
ssl: exports_external.boolean().default(true)
|
|
11470
11475
|
}).default({}),
|
|
11471
|
-
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("
|
|
11476
|
+
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
11472
11477
|
auto_sync_interval_minutes: exports_external.number().default(0),
|
|
11473
11478
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
11474
11479
|
sync: exports_external.object({
|
|
@@ -11499,7 +11504,10 @@ function getConnectionString(dbName) {
|
|
|
11499
11504
|
if (!host || !username) {
|
|
11500
11505
|
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
11501
11506
|
}
|
|
11502
|
-
const password = process.env[password_env]
|
|
11507
|
+
const password = process.env[password_env];
|
|
11508
|
+
if (password === undefined || password === "") {
|
|
11509
|
+
throw new Error(`RDS password not set. Export ${password_env} in your shell or add it to ~/.secrets/hasna/rds/live.env`);
|
|
11510
|
+
}
|
|
11503
11511
|
const sslParam = ssl ? "?sslmode=require" : "";
|
|
11504
11512
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
11505
11513
|
}
|
|
@@ -12067,7 +12075,8 @@ class PgAdapterAsync2 {
|
|
|
12067
12075
|
pool;
|
|
12068
12076
|
constructor(arg) {
|
|
12069
12077
|
if (typeof arg === "string") {
|
|
12070
|
-
|
|
12078
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
12079
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
12071
12080
|
} else {
|
|
12072
12081
|
this.pool = arg;
|
|
12073
12082
|
}
|
|
@@ -12122,18 +12131,10 @@ class PgAdapterAsync2 {
|
|
|
12122
12131
|
// src/sync-schedule.ts
|
|
12123
12132
|
init_config();
|
|
12124
12133
|
import { join as join5, dirname } from "path";
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
const jsPath = join5(dir, "scheduled-sync.js");
|
|
12130
|
-
try {
|
|
12131
|
-
const { existsSync: existsSync5 } = __require("fs");
|
|
12132
|
-
if (existsSync5(tsPath))
|
|
12133
|
-
return tsPath;
|
|
12134
|
-
} catch {}
|
|
12135
|
-
return jsPath;
|
|
12136
|
-
}
|
|
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");
|
|
12137
12138
|
function parseInterval(input) {
|
|
12138
12139
|
const trimmed = input.trim().toLowerCase();
|
|
12139
12140
|
const hourMatch = trimmed.match(/^(\d+)\s*h$/);
|
|
@@ -12172,19 +12173,161 @@ function minutesToCron(minutes) {
|
|
|
12172
12173
|
}
|
|
12173
12174
|
return `*/${minutes} * * * *`;
|
|
12174
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
|
+
}
|
|
12175
12311
|
async function registerSyncSchedule(intervalMinutes) {
|
|
12176
12312
|
if (intervalMinutes <= 0) {
|
|
12177
12313
|
throw new Error("Interval must be a positive number of minutes.");
|
|
12178
12314
|
}
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12315
|
+
mkdirSync5(CONFIG_DIR3, { recursive: true });
|
|
12316
|
+
if (platform() === "darwin") {
|
|
12317
|
+
await registerLaunchd(intervalMinutes);
|
|
12318
|
+
} else {
|
|
12319
|
+
await registerSystemd(intervalMinutes);
|
|
12320
|
+
}
|
|
12182
12321
|
const config = getCloudConfig2();
|
|
12183
12322
|
config.sync.schedule_minutes = intervalMinutes;
|
|
12184
12323
|
saveCloudConfig2(config);
|
|
12185
12324
|
}
|
|
12186
12325
|
async function removeSyncSchedule() {
|
|
12187
|
-
|
|
12326
|
+
if (platform() === "darwin") {
|
|
12327
|
+
await removeLaunchd();
|
|
12328
|
+
} else {
|
|
12329
|
+
await removeSystemd();
|
|
12330
|
+
}
|
|
12188
12331
|
const config = getCloudConfig2();
|
|
12189
12332
|
config.sync.schedule_minutes = 0;
|
|
12190
12333
|
saveCloudConfig2(config);
|
|
@@ -12193,17 +12336,26 @@ function getSyncScheduleStatus() {
|
|
|
12193
12336
|
const config = getCloudConfig2();
|
|
12194
12337
|
const minutes = config.sync.schedule_minutes;
|
|
12195
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
|
+
}
|
|
12196
12347
|
return {
|
|
12197
12348
|
registered,
|
|
12198
12349
|
schedule_minutes: minutes,
|
|
12199
|
-
cron_expression: registered ? minutesToCron(minutes) : null
|
|
12350
|
+
cron_expression: registered ? minutesToCron(minutes) : null,
|
|
12351
|
+
mechanism
|
|
12200
12352
|
};
|
|
12201
12353
|
}
|
|
12202
12354
|
|
|
12203
12355
|
// src/scheduled-sync.ts
|
|
12204
12356
|
init_config();
|
|
12205
12357
|
init_adapter();
|
|
12206
|
-
import { existsSync as
|
|
12358
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
12207
12359
|
import { join as join6 } from "path";
|
|
12208
12360
|
|
|
12209
12361
|
// src/sync-incremental.ts
|
|
@@ -12346,7 +12498,7 @@ function discoverSyncableServices() {
|
|
|
12346
12498
|
if (!entry.isDirectory())
|
|
12347
12499
|
continue;
|
|
12348
12500
|
const dbPath = join6(hasnaDir, entry.name, `${entry.name}.db`);
|
|
12349
|
-
if (
|
|
12501
|
+
if (existsSync6(dbPath)) {
|
|
12350
12502
|
services.push(entry.name);
|
|
12351
12503
|
}
|
|
12352
12504
|
}
|
|
@@ -12369,7 +12521,7 @@ async function runScheduledSync() {
|
|
|
12369
12521
|
};
|
|
12370
12522
|
try {
|
|
12371
12523
|
const dbPath = join6(getDataDir(service), `${service}.db`);
|
|
12372
|
-
if (!
|
|
12524
|
+
if (!existsSync6(dbPath)) {
|
|
12373
12525
|
continue;
|
|
12374
12526
|
}
|
|
12375
12527
|
const local = new SqliteAdapter(dbPath);
|
|
@@ -12412,9 +12564,9 @@ async function runScheduledSync() {
|
|
|
12412
12564
|
}
|
|
12413
12565
|
|
|
12414
12566
|
// src/discover.ts
|
|
12415
|
-
import { readdirSync as readdirSync4, existsSync as
|
|
12567
|
+
import { readdirSync as readdirSync4, existsSync as existsSync7 } from "fs";
|
|
12416
12568
|
import { join as join7 } from "path";
|
|
12417
|
-
import { homedir as
|
|
12569
|
+
import { homedir as homedir6 } from "os";
|
|
12418
12570
|
var SYNC_EXCLUDED_TABLE_PATTERNS = [
|
|
12419
12571
|
/^sqlite_/,
|
|
12420
12572
|
/_fts$/,
|
|
@@ -12426,8 +12578,8 @@ function isSyncExcludedTable(table) {
|
|
|
12426
12578
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
12427
12579
|
}
|
|
12428
12580
|
function discoverServices() {
|
|
12429
|
-
const dataDir = join7(
|
|
12430
|
-
if (!
|
|
12581
|
+
const dataDir = join7(homedir6(), ".hasna");
|
|
12582
|
+
if (!existsSync7(dataDir))
|
|
12431
12583
|
return [];
|
|
12432
12584
|
try {
|
|
12433
12585
|
const entries = readdirSync4(dataDir, { withFileTypes: true });
|
package/dist/config.d.ts
CHANGED
|
@@ -7,17 +7,17 @@ export declare const CloudConfigSchema: z.ZodObject<{
|
|
|
7
7
|
password_env: z.ZodDefault<z.ZodString>;
|
|
8
8
|
ssl: z.ZodDefault<z.ZodBoolean>;
|
|
9
9
|
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
ssl: boolean;
|
|
10
11
|
host: string;
|
|
11
12
|
port: number;
|
|
12
13
|
username: string;
|
|
13
14
|
password_env: string;
|
|
14
|
-
ssl: boolean;
|
|
15
15
|
}, {
|
|
16
|
+
ssl?: boolean | undefined;
|
|
16
17
|
host?: string | undefined;
|
|
17
18
|
port?: number | undefined;
|
|
18
19
|
username?: string | undefined;
|
|
19
20
|
password_env?: string | undefined;
|
|
20
|
-
ssl?: boolean | undefined;
|
|
21
21
|
}>>;
|
|
22
22
|
mode: z.ZodDefault<z.ZodEnum<["local", "cloud", "hybrid"]>>;
|
|
23
23
|
auto_sync_interval_minutes: z.ZodDefault<z.ZodNumber>;
|
|
@@ -31,11 +31,11 @@ export declare const CloudConfigSchema: z.ZodObject<{
|
|
|
31
31
|
}>>;
|
|
32
32
|
}, "strip", z.ZodTypeAny, {
|
|
33
33
|
rds: {
|
|
34
|
+
ssl: boolean;
|
|
34
35
|
host: string;
|
|
35
36
|
port: number;
|
|
36
37
|
username: string;
|
|
37
38
|
password_env: string;
|
|
38
|
-
ssl: boolean;
|
|
39
39
|
};
|
|
40
40
|
mode: "local" | "cloud" | "hybrid";
|
|
41
41
|
auto_sync_interval_minutes: number;
|
|
@@ -45,11 +45,11 @@ export declare const CloudConfigSchema: z.ZodObject<{
|
|
|
45
45
|
};
|
|
46
46
|
}, {
|
|
47
47
|
rds?: {
|
|
48
|
+
ssl?: boolean | undefined;
|
|
48
49
|
host?: string | undefined;
|
|
49
50
|
port?: number | undefined;
|
|
50
51
|
username?: string | undefined;
|
|
51
52
|
password_env?: string | undefined;
|
|
52
|
-
ssl?: boolean | undefined;
|
|
53
53
|
} | undefined;
|
|
54
54
|
mode?: "local" | "cloud" | "hybrid" | undefined;
|
|
55
55
|
auto_sync_interval_minutes?: number | undefined;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoB5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAS5D,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAMD,wBAAgB,cAAc,IAAI,WAAW,CAU5C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAGzD;AAMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoB5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAS5D,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAMD,wBAAgB,cAAc,IAAI,WAAW,CAU5C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAGzD;AAMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAMD,OAAO,EAA4B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGxE,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,CAaxE"}
|
package/dist/index.js
CHANGED
|
@@ -5048,7 +5048,8 @@ class PgAdapter {
|
|
|
5048
5048
|
_client = null;
|
|
5049
5049
|
constructor(arg) {
|
|
5050
5050
|
if (typeof arg === "string") {
|
|
5051
|
-
|
|
5051
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
5052
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
5052
5053
|
} else {
|
|
5053
5054
|
this.pool = arg;
|
|
5054
5055
|
}
|
|
@@ -5175,7 +5176,8 @@ class PgAdapterAsync {
|
|
|
5175
5176
|
pool;
|
|
5176
5177
|
constructor(arg) {
|
|
5177
5178
|
if (typeof arg === "string") {
|
|
5178
|
-
|
|
5179
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
5180
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
5179
5181
|
} else {
|
|
5180
5182
|
this.pool = arg;
|
|
5181
5183
|
}
|
|
@@ -9294,7 +9296,10 @@ function getConnectionString(dbName) {
|
|
|
9294
9296
|
if (!host || !username) {
|
|
9295
9297
|
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
9296
9298
|
}
|
|
9297
|
-
const password = process.env[password_env]
|
|
9299
|
+
const password = process.env[password_env];
|
|
9300
|
+
if (password === undefined || password === "") {
|
|
9301
|
+
throw new Error(`RDS password not set. Export ${password_env} in your shell or add it to ~/.secrets/hasna/rds/live.env`);
|
|
9302
|
+
}
|
|
9298
9303
|
const sslParam = ssl ? "?sslmode=require" : "";
|
|
9299
9304
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
9300
9305
|
}
|
|
@@ -9321,7 +9326,7 @@ var init_config = __esm(() => {
|
|
|
9321
9326
|
password_env: exports_external.string().default("HASNA_RDS_PASSWORD"),
|
|
9322
9327
|
ssl: exports_external.boolean().default(true)
|
|
9323
9328
|
}).default({}),
|
|
9324
|
-
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("
|
|
9329
|
+
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
9325
9330
|
auto_sync_interval_minutes: exports_external.number().default(0),
|
|
9326
9331
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
9327
9332
|
sync: exports_external.object({
|
|
@@ -9342,15 +9347,15 @@ __export(exports_discover, {
|
|
|
9342
9347
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
9343
9348
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
9344
9349
|
});
|
|
9345
|
-
import { readdirSync as readdirSync3, existsSync as
|
|
9350
|
+
import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
|
|
9346
9351
|
import { join as join6 } from "path";
|
|
9347
|
-
import { homedir as
|
|
9352
|
+
import { homedir as homedir5 } from "os";
|
|
9348
9353
|
function isSyncExcludedTable(table) {
|
|
9349
9354
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
9350
9355
|
}
|
|
9351
9356
|
function discoverServices() {
|
|
9352
|
-
const dataDir = join6(
|
|
9353
|
-
if (!
|
|
9357
|
+
const dataDir = join6(homedir5(), ".hasna");
|
|
9358
|
+
if (!existsSync6(dataDir))
|
|
9354
9359
|
return [];
|
|
9355
9360
|
try {
|
|
9356
9361
|
const entries = readdirSync3(dataDir, { withFileTypes: true });
|
|
@@ -9371,8 +9376,8 @@ function discoverSyncableServices2() {
|
|
|
9371
9376
|
return local.filter((s) => pgSet.has(s));
|
|
9372
9377
|
}
|
|
9373
9378
|
function getServiceDbPath(service) {
|
|
9374
|
-
const dataDir = join6(
|
|
9375
|
-
if (!
|
|
9379
|
+
const dataDir = join6(homedir5(), ".hasna", service);
|
|
9380
|
+
if (!existsSync6(dataDir))
|
|
9376
9381
|
return null;
|
|
9377
9382
|
const candidates = [
|
|
9378
9383
|
join6(dataDir, `${service}.db`),
|
|
@@ -9388,7 +9393,7 @@ function getServiceDbPath(service) {
|
|
|
9388
9393
|
}
|
|
9389
9394
|
} catch {}
|
|
9390
9395
|
for (const p of candidates) {
|
|
9391
|
-
if (
|
|
9396
|
+
if (existsSync6(p))
|
|
9392
9397
|
return p;
|
|
9393
9398
|
}
|
|
9394
9399
|
return null;
|
|
@@ -10531,18 +10536,10 @@ async function runScheduledSync() {
|
|
|
10531
10536
|
// src/sync-schedule.ts
|
|
10532
10537
|
init_config();
|
|
10533
10538
|
import { join as join5, dirname } from "path";
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
const jsPath = join5(dir, "scheduled-sync.js");
|
|
10539
|
-
try {
|
|
10540
|
-
const { existsSync: existsSync5 } = __require("fs");
|
|
10541
|
-
if (existsSync5(tsPath))
|
|
10542
|
-
return tsPath;
|
|
10543
|
-
} catch {}
|
|
10544
|
-
return jsPath;
|
|
10545
|
-
}
|
|
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");
|
|
10546
10543
|
function parseInterval(input) {
|
|
10547
10544
|
const trimmed = input.trim().toLowerCase();
|
|
10548
10545
|
const hourMatch = trimmed.match(/^(\d+)\s*h$/);
|
|
@@ -10581,19 +10578,161 @@ function minutesToCron(minutes) {
|
|
|
10581
10578
|
}
|
|
10582
10579
|
return `*/${minutes} * * * *`;
|
|
10583
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
|
+
}
|
|
10584
10716
|
async function registerSyncSchedule(intervalMinutes) {
|
|
10585
10717
|
if (intervalMinutes <= 0) {
|
|
10586
10718
|
throw new Error("Interval must be a positive number of minutes.");
|
|
10587
10719
|
}
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10720
|
+
mkdirSync3(CONFIG_DIR2, { recursive: true });
|
|
10721
|
+
if (platform() === "darwin") {
|
|
10722
|
+
await registerLaunchd(intervalMinutes);
|
|
10723
|
+
} else {
|
|
10724
|
+
await registerSystemd(intervalMinutes);
|
|
10725
|
+
}
|
|
10591
10726
|
const config = getCloudConfig();
|
|
10592
10727
|
config.sync.schedule_minutes = intervalMinutes;
|
|
10593
10728
|
saveCloudConfig(config);
|
|
10594
10729
|
}
|
|
10595
10730
|
async function removeSyncSchedule() {
|
|
10596
|
-
|
|
10731
|
+
if (platform() === "darwin") {
|
|
10732
|
+
await removeLaunchd();
|
|
10733
|
+
} else {
|
|
10734
|
+
await removeSystemd();
|
|
10735
|
+
}
|
|
10597
10736
|
const config = getCloudConfig();
|
|
10598
10737
|
config.sync.schedule_minutes = 0;
|
|
10599
10738
|
saveCloudConfig(config);
|
|
@@ -10602,10 +10741,19 @@ function getSyncScheduleStatus() {
|
|
|
10602
10741
|
const config = getCloudConfig();
|
|
10603
10742
|
const minutes = config.sync.schedule_minutes;
|
|
10604
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
|
+
}
|
|
10605
10752
|
return {
|
|
10606
10753
|
registered,
|
|
10607
10754
|
schedule_minutes: minutes,
|
|
10608
|
-
cron_expression: registered ? minutesToCron(minutes) : null
|
|
10755
|
+
cron_expression: registered ? minutesToCron(minutes) : null,
|
|
10756
|
+
mechanism
|
|
10609
10757
|
};
|
|
10610
10758
|
}
|
|
10611
10759
|
// src/pg-migrate.ts
|
package/dist/mcp/index.js
CHANGED
|
@@ -24468,7 +24468,8 @@ class PgAdapter {
|
|
|
24468
24468
|
_client = null;
|
|
24469
24469
|
constructor(arg) {
|
|
24470
24470
|
if (typeof arg === "string") {
|
|
24471
|
-
|
|
24471
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
24472
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
24472
24473
|
} else {
|
|
24473
24474
|
this.pool = arg;
|
|
24474
24475
|
}
|
|
@@ -24620,7 +24621,7 @@ var CloudConfigSchema = exports_external.object({
|
|
|
24620
24621
|
password_env: exports_external.string().default("HASNA_RDS_PASSWORD"),
|
|
24621
24622
|
ssl: exports_external.boolean().default(true)
|
|
24622
24623
|
}).default({}),
|
|
24623
|
-
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("
|
|
24624
|
+
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
24624
24625
|
auto_sync_interval_minutes: exports_external.number().default(0),
|
|
24625
24626
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
24626
24627
|
sync: exports_external.object({
|
|
@@ -24646,7 +24647,10 @@ function getConnectionString(dbName) {
|
|
|
24646
24647
|
if (!host || !username) {
|
|
24647
24648
|
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
24648
24649
|
}
|
|
24649
|
-
const password = process.env[password_env]
|
|
24650
|
+
const password = process.env[password_env];
|
|
24651
|
+
if (password === undefined || password === "") {
|
|
24652
|
+
throw new Error(`RDS password not set. Export ${password_env} in your shell or add it to ~/.secrets/hasna/rds/live.env`);
|
|
24653
|
+
}
|
|
24650
24654
|
const sslParam = ssl ? "?sslmode=require" : "";
|
|
24651
24655
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
24652
24656
|
}
|
|
@@ -25051,7 +25055,7 @@ var CloudConfigSchema2 = exports_external.object({
|
|
|
25051
25055
|
password_env: exports_external.string().default("HASNA_RDS_PASSWORD"),
|
|
25052
25056
|
ssl: exports_external.boolean().default(true)
|
|
25053
25057
|
}).default({}),
|
|
25054
|
-
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("
|
|
25058
|
+
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
25055
25059
|
auto_sync_interval_minutes: exports_external.number().default(0),
|
|
25056
25060
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
25057
25061
|
sync: exports_external.object({
|
|
@@ -25220,7 +25224,8 @@ class PgAdapterAsync {
|
|
|
25220
25224
|
pool;
|
|
25221
25225
|
constructor(arg) {
|
|
25222
25226
|
if (typeof arg === "string") {
|
|
25223
|
-
|
|
25227
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
25228
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
25224
25229
|
} else {
|
|
25225
25230
|
this.pool = arg;
|
|
25226
25231
|
}
|
package/dist/scheduled-sync.js
CHANGED
|
@@ -9014,7 +9014,8 @@ class PgAdapterAsync {
|
|
|
9014
9014
|
pool;
|
|
9015
9015
|
constructor(arg) {
|
|
9016
9016
|
if (typeof arg === "string") {
|
|
9017
|
-
|
|
9017
|
+
const sslConfig = arg.includes("sslmode=require") || arg.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
|
|
9018
|
+
this.pool = new esm_default.Pool({ connectionString: arg, ssl: sslConfig });
|
|
9018
9019
|
} else {
|
|
9019
9020
|
this.pool = arg;
|
|
9020
9021
|
}
|
|
@@ -9095,7 +9096,7 @@ var CloudConfigSchema = exports_external.object({
|
|
|
9095
9096
|
password_env: exports_external.string().default("HASNA_RDS_PASSWORD"),
|
|
9096
9097
|
ssl: exports_external.boolean().default(true)
|
|
9097
9098
|
}).default({}),
|
|
9098
|
-
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("
|
|
9099
|
+
mode: exports_external.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
9099
9100
|
auto_sync_interval_minutes: exports_external.number().default(0),
|
|
9100
9101
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
9101
9102
|
sync: exports_external.object({
|
|
@@ -9121,7 +9122,10 @@ function getConnectionString(dbName) {
|
|
|
9121
9122
|
if (!host || !username) {
|
|
9122
9123
|
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
9123
9124
|
}
|
|
9124
|
-
const password = process.env[password_env]
|
|
9125
|
+
const password = process.env[password_env];
|
|
9126
|
+
if (password === undefined || password === "") {
|
|
9127
|
+
throw new Error(`RDS password not set. Export ${password_env} in your shell or add it to ~/.secrets/hasna/rds/live.env`);
|
|
9128
|
+
}
|
|
9125
9129
|
const sslParam = ssl ? "?sslmode=require" : "";
|
|
9126
9130
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
9127
9131
|
}
|
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