src/loadSettings.js

/**
 * @file
 * @brief Builds the live settings object: clone defaults, merge user-settings.json, migrate any legacy user-settings.js. Exposes settings() / userSettings() / save / reload / replace. Notes: notes/systems/cli.md.
 */

import fs from "node:fs";
import { createRequire } from "node:module";
import _ from "lodash";

import createMissingUserSettings from "./createMissingUserSettings.js";
import diffSettings from "./diffSettings.js";

// Load settings
import basicSettings from "./settings.js";
import imageSettings from "./image-settings.js";
import upscaleSettings from "./upscale-settings.js";
import serverSettings from "./server-settings.js";

// Scoped require used only for the optional, synchronous legacy `user-settings.js`
// migration below (createRequire can load the old CommonJS settings file).
const require = createRequire(import.meta.url);

const isServer = process.env.server !== undefined;

// Ensure user-settings is created
createMissingUserSettings();

// Add whether we're runing inside a server or not
basicSettings.isServer = isServer;

// Combine them into a single object
// We use deep-clone to prevent the mfrom being modified
// This also prevents a bug where converting legacy settings which modifies these files
// therefore also modies this copy
const defSettings = _.cloneDeep({
  settings: basicSettings,
  imageSettings,
  upscaleSettings,
  serverSettings,
});

let settings;

/**
 * (Re)build the live settings object: clone the defaults, merge `user-settings.json`,
 * and migrate a legacy `user-settings.js` if present.
 * @returns {void}
 */
// Does a complicated process of reloading settings
function reloadSettings() {
  // Load "user-settings.json"
  let userSettings = JSON.parse(fs.readFileSync("./user-settings.json").toString());

  // Clone default settings as a basis
  settings = _.cloneDeep(defSettings);

  // Merge user settings into main settings
  if (userSettings.settings != undefined) _.merge(settings.settings, userSettings.settings);

  if (userSettings.imageSettings != undefined)
    _.merge(settings.imageSettings, userSettings.imageSettings);

  if (userSettings.upscaleSettings != undefined)
    _.merge(settings.upscaleSettings, userSettings.upscaleSettings);

  if (userSettings.serverSettings != undefined)
    _.merge(settings.serverSettings, userSettings.serverSettings);

  // Merge legacy user-settings.js if it exists
  try {
    // Import legacy settings
    const legacySettings = require("../user-settings.js");

    console.log("Found old user-settings.js, converting to user-settings.json...");

    // Do a diff on it to extract the actual changes
    const legacyDiff = diffSettings(legacySettings, defSettings);

    // Merge changes in
    _.merge(settings, legacyDiff);

    // Remove legacy file
    fs.unlinkSync("./user-settings.js");
  } catch (err) {}
}

// Do initial settings load now
reloadSettings();

/**
 * Compute the settings that differ from defaults, with internal-only / runtime
 * fields (progress, lastCmd, animation bookkeeping, …) stripped.
 * @returns {object} The persistable user settings.
 */
// Allows obtaining the user dettings that differ from the main settings
function userSettings() {
  // Get diff between default and user settings
  const ret = diffSettings(settings, defSettings);

  // Remove these internal only settings
  delete ret.settings.origPrompt;
  delete ret.settings.randomPrompt;
  delete ret.settings.animationPromptSet;

  delete ret.imageSettings.lastCmd;
  delete ret.imageSettings.variationOf;
  delete ret.imageSettings.origPostPrompt;
  delete ret.imageSettings.autoIncludedFx;
  delete ret.imageSettings.autoIncludedArtists;
  delete ret.imageSettings.animationOf;
  delete ret.imageSettings.animationFrames;
  delete ret.imageSettings.animationOfImg;

  delete ret.imageSettings.resultPrompts;
  delete ret.imageSettings.resultImages;

  delete ret.imageSettings.progressOngoing;
  delete ret.imageSettings.progressPercent;
  delete ret.imageSettings.progressEta;
  delete ret.imageSettings.progressCurImg;
  delete ret.imageSettings.progressTotalImg;
  delete ret.imageSettings.progressCurStep;
  delete ret.imageSettings.progressTotalSteps;
  delete ret.imageSettings.progressCurPrompt;
  delete ret.imageSettings.progressTotalPrompts;

  delete ret.imageSettings.progressUpscaling;

  // Return
  return ret;
}

/**
 * Persist `userSettings()` to `user-settings.json`.
 * @returns {void}
 */
// Save User Settings
function saveSettings() {
  // Save user settings as user-settings.json
  fs.writeFileSync("./user-settings.json", JSON.stringify(userSettings(), null, 4));
}

// Do initial save now
saveSettings();

export default {
  // Settings (Use function to get up-to-date settings)
  settings() {
    return settings;
  },

  userSettings,

  // Default settings (Debug, use deep clone to prevent tampering)
  defSettings() {
    return _.cloneDeep(defSettings);
  },

  // Replace Settings
  replaceSettings(newSettings) {
    settings = newSettings;
  },

  reloadSettings,
  saveSettings,
};