dsh-home-hosted 0.7.3 → 0.7.4

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/lib/index.js CHANGED
@@ -2484,6 +2484,9 @@ function migrationRefusal(home) {
2484
2484
  var ConfigLayoutError = class extends Error {
2485
2485
  name = "ConfigLayoutError";
2486
2486
  };
2487
+ var ConfigPatchError = class extends Error {
2488
+ name = "ConfigPatchError";
2489
+ };
2487
2490
  function readConfig(home, workspace = DEFAULT_WORKSPACE) {
2488
2491
  const refusal = migrationRefusal(home);
2489
2492
  if (refusal !== null)
@@ -2494,8 +2497,15 @@ function readConfig(home, workspace = DEFAULT_WORKSPACE) {
2494
2497
  const exists = fs8.existsSync(file);
2495
2498
  return { raw: null, exists, error: exists ? "the workspace config file could not be parsed" : null };
2496
2499
  }
2500
+ if (!isRecordValue(raw))
2501
+ return { raw: null, exists: true, error: "the workspace config file does not contain a JSON object" };
2497
2502
  if (!Array.isArray(raw.servers) && raw.servers !== void 0)
2498
2503
  return { raw: null, exists: true, error: "the workspace config file has a servers field that is not an array" };
2504
+ if (Array.isArray(raw.servers)) {
2505
+ const index = raw.servers.findIndex((entry) => !isRecordValue(entry));
2506
+ if (index >= 0)
2507
+ return { raw: null, exists: true, error: `the workspace config file has an entry at servers[${index}] that is not an object` };
2508
+ }
2499
2509
  return { raw, exists: true, error: null };
2500
2510
  }
2501
2511
  function writeConfig(home, raw, writtenBy = "dsh-home-hosted", workspace = DEFAULT_WORKSPACE) {
@@ -2515,7 +2525,9 @@ function readGlobalSettings(home) {
2515
2525
  const exists = fs8.existsSync(file);
2516
2526
  return { raw: null, exists, error: exists ? "the panel settings file could not be parsed" : null };
2517
2527
  }
2518
- if (raw.control !== void 0 && (typeof raw.control !== "object" || raw.control === null || Array.isArray(raw.control)))
2528
+ if (!isRecordValue(raw))
2529
+ return { raw: null, exists: true, error: "the panel settings file does not contain a JSON object" };
2530
+ if (raw.control !== void 0 && !isRecordValue(raw.control))
2519
2531
  return { raw: null, exists: true, error: "the panel settings file has a control field that is not an object" };
2520
2532
  return { raw, exists: true, error: null };
2521
2533
  }
@@ -2529,7 +2541,7 @@ function setControl(home, patch, writtenBy = "dsh-home-hosted") {
2529
2541
  const read = readGlobalSettings(home);
2530
2542
  if (read.error !== null)
2531
2543
  throw new ConfigLayoutError(read.error);
2532
- writeGlobalSettings(home, { ...read.raw ?? {}, control: { ...read.raw?.control ?? {}, ...patch } }, writtenBy);
2544
+ writeGlobalSettings(home, patchControl(read.raw ?? {}, patch), writtenBy);
2533
2545
  }
2534
2546
  function refuseUnmigrated(home) {
2535
2547
  const refusal = migrationRefusal(home);
@@ -2548,17 +2560,61 @@ function upsertEntry(raw, entry) {
2548
2560
  servers.push(entry);
2549
2561
  return { ...raw, servers };
2550
2562
  }
2563
+ var SERVER_MERGE_KEYS = /* @__PURE__ */ new Set(["restart", "health", "stop"]);
2564
+ var CONTROL_MERGE_KEYS = /* @__PURE__ */ new Set(["auth", "tls"]);
2565
+ function isRecordValue(value) {
2566
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2567
+ }
2568
+ function mergeGroup(target, patch) {
2569
+ const merged = { ...target };
2570
+ for (const [key, value] of Object.entries(patch)) {
2571
+ if (value === void 0)
2572
+ continue;
2573
+ if (value === null) {
2574
+ delete merged[key];
2575
+ continue;
2576
+ }
2577
+ if (isRecordValue(value) && isRecordValue(merged[key])) {
2578
+ merged[key] = mergeGroup(merged[key], value);
2579
+ continue;
2580
+ }
2581
+ merged[key] = value;
2582
+ }
2583
+ return merged;
2584
+ }
2551
2585
  function patchEntry(raw, id, patch) {
2586
+ for (const key of SERVER_MERGE_KEYS) {
2587
+ if (patch[key] === null)
2588
+ throw new ConfigPatchError(`${key} must be an object (was null): the panel refuses to boot a config that carries it`);
2589
+ }
2552
2590
  const servers = (raw.servers ?? []).map((entry) => {
2553
2591
  if (entry.id !== id)
2554
2592
  return entry;
2555
- const merged = { ...entry, ...patch };
2556
- if (patch.stop !== void 0 && typeof entry.stop === "object" && entry.stop !== null)
2557
- merged.stop = { ...entry.stop, ...patch.stop };
2593
+ const merged = { ...entry };
2594
+ for (const [key, value] of Object.entries(patch)) {
2595
+ if (value === void 0)
2596
+ continue;
2597
+ if (SERVER_MERGE_KEYS.has(key) && isRecordValue(value) && isRecordValue(merged[key]))
2598
+ merged[key] = mergeGroup(merged[key], value);
2599
+ else
2600
+ merged[key] = value;
2601
+ }
2558
2602
  return merged;
2559
2603
  });
2560
2604
  return { ...raw, servers };
2561
2605
  }
2606
+ function patchControl(raw, patch) {
2607
+ const control = { ...raw.control ?? {} };
2608
+ for (const [key, value] of Object.entries(patch)) {
2609
+ if (value === void 0)
2610
+ continue;
2611
+ if (CONTROL_MERGE_KEYS.has(key) && isRecordValue(value) && isRecordValue(control[key]))
2612
+ control[key] = mergeGroup(control[key], value);
2613
+ else
2614
+ control[key] = value;
2615
+ }
2616
+ return { ...raw, control };
2617
+ }
2562
2618
  function removeEntry(raw, id) {
2563
2619
  return { ...raw, servers: (raw.servers ?? []).filter((entry) => entry.id !== id) };
2564
2620
  }
@@ -4455,7 +4511,7 @@ var HomeHostedError = class extends Error {
4455
4511
  this.name = "HomeHostedError";
4456
4512
  }
4457
4513
  };
4458
- function isRecordValue(value) {
4514
+ function isRecordValue2(value) {
4459
4515
  return typeof value === "object" && value !== null && !Array.isArray(value);
4460
4516
  }
4461
4517
  function foreignView(entry, workspace) {
@@ -4954,7 +5010,7 @@ var HomeHostedService = class extends Service {
4954
5010
  const raw = readJson(this.snapshotsFile);
4955
5011
  if (raw === null)
4956
5012
  return {};
4957
- if (raw.version === 2 && isRecordValue(raw.entries))
5013
+ if (raw.version === 2 && isRecordValue2(raw.entries))
4958
5014
  return raw.workspace === this.managedWorkspace() ? raw.entries : {};
4959
5015
  return this.managedWorkspace() === DEFAULT_WORKSPACE ? raw : {};
4960
5016
  }
@@ -5843,6 +5899,10 @@ var HomeHostedService = class extends Service {
5843
5899
  const target = await this.targetHome(input.home);
5844
5900
  const id = String(input.id);
5845
5901
  const patch = input.patch ?? {};
5902
+ for (const key of ["restart", "health", "stop"]) {
5903
+ if (patch[key] === null)
5904
+ throw new HomeHostedError(`${key} must be an object (was null)`, "INVALID_PATCH");
5905
+ }
5846
5906
  const workspace = this.callWorkspace(input, target.home, target.foreign);
5847
5907
  if (!target.foreign)
5848
5908
  return await (await this.requireClient()).updateServer(id, patch, workspace);