wattetheria 0.4.2 → 0.4.3

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.
Files changed (2) hide show
  1. package/lib/cli.js +86 -9
  2. package/package.json +1 -1
package/lib/cli.js CHANGED
@@ -8,8 +8,10 @@ const readline = require("node:readline");
8
8
 
9
9
  const PACKAGE_ROOT = path.resolve(__dirname, "..");
10
10
  const PACKAGE_JSON = require(path.join(PACKAGE_ROOT, "package.json"));
11
+ const WATTETHERIA_HOME_DIR = path.join(os.homedir(), ".wattetheria");
11
12
  const DEFAULT_DEPLOY_DIR = path.join(os.homedir(), ".wattetheria", "deploy");
12
13
  const DEFAULT_PROJECT_NAME = "wattetheria";
14
+ const POSTGRES_VOLUME_SERVICE_NAME = "wattswarm_pg_data";
13
15
  const DEFAULT_COMMAND = "help";
14
16
  const RELEASE_ENV_TEMPLATE_PATH = path.join(PACKAGE_ROOT, ".env.release");
15
17
  const IMAGE_KEYS = [
@@ -85,7 +87,7 @@ Options:
85
87
  --force Refresh deployment defaults and compose assets
86
88
  --no-health-checks Skip HTTP health checks
87
89
  --volumes With uninstall, remove named docker volumes
88
- --purge With uninstall, remove the deployment directory
90
+ --purge With uninstall, remove ~/.wattetheria and PostgreSQL data
89
91
  --data-dir <path> With mcp-proxy or doctor, override Wattetheria host state directory
90
92
  --control-plane <url> With mcp-proxy or doctor, override local control-plane endpoint
91
93
 
@@ -955,6 +957,74 @@ function runCompose(options, args, capture = false) {
955
957
  return result;
956
958
  }
957
959
 
960
+ function runDocker(args, capture = false) {
961
+ const dockerCommand = resolveDockerCommand() || "docker";
962
+ const result = spawnSync(dockerCommand, args, {
963
+ stdio: capture ? "pipe" : "inherit",
964
+ encoding: capture ? "utf8" : undefined
965
+ });
966
+ if (result.error) {
967
+ throw result.error;
968
+ }
969
+ if (result.status !== 0) {
970
+ const stderr = capture ? (result.stderr || "").trim() : "";
971
+ throw new Error(stderr || `docker ${args.join(" ")} failed`);
972
+ }
973
+ return result;
974
+ }
975
+
976
+ function postgresVolumeName(options) {
977
+ return `${options.projectName}_${POSTGRES_VOLUME_SERVICE_NAME}`;
978
+ }
979
+
980
+ function dockerVolumeExists(volumeName) {
981
+ const dockerCommand = resolveDockerCommand() || "docker";
982
+ const result = spawnSync(dockerCommand, ["volume", "inspect", volumeName], {
983
+ stdio: "ignore"
984
+ });
985
+ return !result.error && result.status === 0;
986
+ }
987
+
988
+ function removeDockerVolumeIfExists(volumeName) {
989
+ if (!dockerVolumeExists(volumeName)) {
990
+ console.log(`PostgreSQL volume not found: ${volumeName}`);
991
+ return;
992
+ }
993
+ runDocker(["volume", "rm", volumeName]);
994
+ if (dockerVolumeExists(volumeName)) {
995
+ throw new Error(`PostgreSQL volume still exists after removal: ${volumeName}`);
996
+ }
997
+ console.log(`Removed PostgreSQL volume: ${volumeName}`);
998
+ }
999
+
1000
+ function comparablePath(filePath) {
1001
+ const resolved = path.resolve(filePath);
1002
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
1003
+ }
1004
+
1005
+ function pathIsInsideOrEqual(childPath, parentPath) {
1006
+ const child = comparablePath(childPath);
1007
+ const parent = comparablePath(parentPath);
1008
+ const relative = path.relative(parent, child);
1009
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
1010
+ }
1011
+
1012
+ function removeWattetheriaHomeDir() {
1013
+ const homeDir = WATTETHERIA_HOME_DIR;
1014
+ if (!fs.existsSync(homeDir)) {
1015
+ console.log(`Wattetheria home directory not found: ${homeDir}`);
1016
+ return;
1017
+ }
1018
+ if (pathIsInsideOrEqual(process.cwd(), homeDir)) {
1019
+ process.chdir(os.homedir());
1020
+ }
1021
+ fs.rmSync(homeDir, { recursive: true, force: true });
1022
+ if (fs.existsSync(homeDir)) {
1023
+ throw new Error(`Wattetheria home directory still exists after removal: ${homeDir}`);
1024
+ }
1025
+ console.log(`Removed Wattetheria home directory: ${homeDir}`);
1026
+ }
1027
+
958
1028
  const RELEASE_SERVICES = [
959
1029
  "kernel",
960
1030
  "wattswarm-postgres",
@@ -1268,14 +1338,21 @@ async function stop(options) {
1268
1338
 
1269
1339
  async function uninstall(options) {
1270
1340
  await ensureDockerAvailable();
1271
- const args = ["down"];
1272
- if (options.volumes) {
1273
- args.push("-v");
1274
- }
1275
- runCompose(options, args);
1276
- if (options.purge && fs.existsSync(options.dir)) {
1277
- fs.rmSync(options.dir, { recursive: true, force: true });
1278
- console.log(`Removed deployment directory: ${options.dir}`);
1341
+ const hasDeploymentFiles = fs.existsSync(composeFilePath(options)) && fs.existsSync(envFilePath(options));
1342
+ if (hasDeploymentFiles) {
1343
+ const args = ["down"];
1344
+ if (options.purge) {
1345
+ args.push("--remove-orphans");
1346
+ } else if (options.volumes) {
1347
+ args.push("-v");
1348
+ }
1349
+ runCompose(options, args);
1350
+ } else {
1351
+ console.log(`Deployment compose/env files not found in: ${options.dir}`);
1352
+ }
1353
+ if (options.purge) {
1354
+ removeDockerVolumeIfExists(postgresVolumeName(options));
1355
+ removeWattetheriaHomeDir();
1279
1356
  }
1280
1357
  }
1281
1358
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wattetheria",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Wattetheria deployment CLI",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "commonjs",