hostctl 0.1.47 → 0.1.48

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/README.md CHANGED
@@ -206,7 +206,6 @@ Key `TaskContext` capabilities (see [`docs/task-api.md`](docs/task-api.md) for d
206
206
  - **Version mismatch**: keep `package.json`, `src/version.ts`, and `jsr.json` in sync before releasing.
207
207
  - **SSH failures**: verify inventory entries (hostname, user, auth) and only pass `-r` when you intend remote execution.
208
208
  - **Secrets**: when inventories are encrypted, ensure `AGE_IDS` includes the private keys so `hostctl` can decrypt secrets.
209
- - **Environment drift**: run `hostctl runtime` to inspect prerequisites or `hostctl runtime install` to bootstrap them.
210
209
 
211
210
  ## Developer workflow
212
211
 
@@ -3886,11 +3886,168 @@ var LocalRuntime = class {
3886
3886
  }
3887
3887
  };
3888
3888
 
3889
+ // src/zip.ts
3890
+ import AdmZip from "adm-zip";
3891
+
3892
+ // src/hash.ts
3893
+ import { createHash } from "crypto";
3894
+
3895
+ // src/param-map.ts
3896
+ import { match as match2 } from "ts-pattern";
3897
+ var ParamMap = class _ParamMap {
3898
+ /**
3899
+ * Parses CLI arguments of the form key:value, only supporting top-level keys.
3900
+ * Only the first colon is treated as a key/value separator; all subsequent colons are part of the value.
3901
+ * This means nested keys are not supported, but values can contain colons (e.g., URLs, JSON, etc.).
3902
+ */
3903
+ static parse(cliArguments) {
3904
+ const root = new _ParamMap();
3905
+ A2(cliArguments).each((arg) => {
3906
+ const idx = arg.indexOf(":");
3907
+ if (idx === -1) {
3908
+ root.set([arg], "true");
3909
+ return;
3910
+ }
3911
+ const key = arg.slice(0, idx);
3912
+ const value = arg.slice(idx + 1);
3913
+ root.set([key], value);
3914
+ });
3915
+ return root;
3916
+ }
3917
+ map;
3918
+ constructor() {
3919
+ this.map = /* @__PURE__ */ new Map();
3920
+ }
3921
+ get(keyPath) {
3922
+ const keys3 = keyPath.split(":");
3923
+ let node = this;
3924
+ for (let i = 0; i < keys3.length; i++) {
3925
+ if (!(node instanceof _ParamMap)) return void 0;
3926
+ node = node.map.get(keys3[i]);
3927
+ if (node === void 0) return void 0;
3928
+ }
3929
+ if (node instanceof _ParamMap) {
3930
+ return node.toObject();
3931
+ }
3932
+ return this.stringToJsonObj(node);
3933
+ }
3934
+ set(keyPath, value) {
3935
+ const keyParts = Array.isArray(keyPath) ? keyPath : keyPath.split(":");
3936
+ let node = this;
3937
+ for (let i = 0; i < keyParts.length - 1; i++) {
3938
+ const key = keyParts[i];
3939
+ let nextNode = node.map.get(key);
3940
+ if (!(nextNode instanceof _ParamMap)) {
3941
+ nextNode = new _ParamMap();
3942
+ node.map.set(key, nextNode);
3943
+ }
3944
+ node = nextNode;
3945
+ }
3946
+ const lastKey = keyParts[keyParts.length - 1];
3947
+ node.map.set(lastKey, value);
3948
+ }
3949
+ toObject() {
3950
+ let obj = {};
3951
+ M(this.map).each(([k, v]) => {
3952
+ obj[k] = v instanceof _ParamMap ? v.toObject() : this.stringToJsonObj(v);
3953
+ });
3954
+ return obj;
3955
+ }
3956
+ stringToJsonObj(str) {
3957
+ if (typeof str !== "string") return str;
3958
+ const trimmed = str.trim();
3959
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
3960
+ try {
3961
+ return JSON.parse(trimmed);
3962
+ } catch {
3963
+ }
3964
+ }
3965
+ return match2(str).when(matches(/,/), (s) => {
3966
+ return VP(s.split(",")).map((v) => v.trim()).compact([""]).map((v) => this.stringToJsonObj(v)).value;
3967
+ }).when(isNumeric, (s) => parseFloat(s)).when(
3968
+ (s) => s.trim() === "",
3969
+ () => ""
3970
+ ).otherwise(() => str);
3971
+ }
3972
+ };
3973
+
3974
+ // src/runtime.ts
3975
+ import * as z from "zod";
3976
+
3977
+ // src/version.ts
3978
+ var version = "0.1.48";
3979
+
3980
+ // src/app.ts
3981
+ import { retryUntilDefined } from "ts-retry";
3982
+ import { Mutex as Mutex3 } from "async-mutex";
3983
+ import { match as match5 } from "ts-pattern";
3984
+
3985
+ // src/core/remote/runAllRemote.ts
3986
+ var RunParamsSchema = z.object({
3987
+ taskFn: z.custom((value) => typeof value === "function", {
3988
+ message: "taskFn must be a function"
3989
+ }),
3990
+ params: z.any()
3991
+ });
3992
+ var RunResultSchema = z.record(z.string(), z.any());
3993
+ function serializeError(value) {
3994
+ if (value instanceof Error) {
3995
+ const err = value;
3996
+ return {
3997
+ error: err.message,
3998
+ name: err.name,
3999
+ code: err.code,
4000
+ stack: err.stack
4001
+ };
4002
+ }
4003
+ if (value && typeof value === "object") {
4004
+ return { error: JSON.stringify(value) };
4005
+ }
4006
+ return { error: String(value) };
4007
+ }
4008
+ async function run(context) {
4009
+ const { params, ssh } = context;
4010
+ const { taskFn, params: taskParams } = params;
4011
+ const remoteResults = await ssh([], async (remoteContext) => {
4012
+ const hostIdentifier = remoteContext.host?.alias || remoteContext.host?.shortName || "unknown_remote_host";
4013
+ remoteContext.debug(`[${hostIdentifier}] Connected via SSH.`);
4014
+ try {
4015
+ const result = await remoteContext.run(taskFn(taskParams));
4016
+ remoteContext.debug(`[${hostIdentifier}] Remote task result:`, JSON.stringify(result));
4017
+ if (result instanceof Error) {
4018
+ remoteContext.warn?.(
4019
+ `[${hostIdentifier}] Remote task returned an Error instance. Converting to serializable object.`
4020
+ );
4021
+ return serializeError(result);
4022
+ }
4023
+ return result;
4024
+ } catch (e) {
4025
+ remoteContext.debug(`[${hostIdentifier}] Failed to run remote task:`, e.message);
4026
+ return serializeError(e);
4027
+ }
4028
+ });
4029
+ const normalizedEntries = Object.entries(remoteResults ?? {}).map(([hostAlias, value]) => {
4030
+ if (value instanceof Error) {
4031
+ return [hostAlias, serializeError(value)];
4032
+ }
4033
+ return [hostAlias, value];
4034
+ });
4035
+ return Object.fromEntries(normalizedEntries);
4036
+ }
4037
+ var runAllRemote_default = task(run, {
4038
+ description: "run a task on all selected hosts",
4039
+ inputSchema: RunParamsSchema,
4040
+ outputSchema: RunResultSchema
4041
+ });
4042
+
4043
+ // src/commands/pkg/package-manager.ts
4044
+ import { promises as fs6 } from "fs";
4045
+
3889
4046
  // src/node-runtime.ts
3890
4047
  import os3 from "os";
3891
4048
  import axios2 from "axios";
3892
4049
  import * as cheerio from "cheerio";
3893
- import { match as match3 } from "ts-pattern";
4050
+ import { match as match4 } from "ts-pattern";
3894
4051
  import which from "which";
3895
4052
 
3896
4053
  // src/http.ts
@@ -3935,14 +4092,14 @@ async function downloadFile(url, dest) {
3935
4092
  import decompress from "decompress";
3936
4093
  import decompressTarGzPlugin from "decompress-targz";
3937
4094
  import decompressZipPlugin from "decompress-unzip";
3938
- import { match as match2, P as P2 } from "ts-pattern";
4095
+ import { match as match3, P as P2 } from "ts-pattern";
3939
4096
  import decompressTarPlugin from "decompress-tar";
3940
4097
  import { fileTypeFromBuffer } from "file-type";
3941
4098
  import { getStreamAsBuffer } from "get-stream";
3942
4099
  import xzdecompress from "xz-decompress";
3943
4100
  async function unarchive(inputPath, outputPath) {
3944
4101
  const filename = File.basename(inputPath);
3945
- return await match2(filename).with(P2.string.regex(/.tar.xz$/), () => decompressTarXz(inputPath, outputPath)).with(P2.string.regex(/.tar.gz$/), () => decompressTarGz(inputPath, outputPath)).with(P2.string.regex(/.zip$/), () => decompressZip(inputPath, outputPath)).otherwise(() => {
4102
+ return await match3(filename).with(P2.string.regex(/.tar.xz$/), () => decompressTarXz(inputPath, outputPath)).with(P2.string.regex(/.tar.gz$/), () => decompressTarGz(inputPath, outputPath)).with(P2.string.regex(/.zip$/), () => decompressZip(inputPath, outputPath)).otherwise(() => {
3946
4103
  throw new Error(`unable to decompress unknown file type: ${inputPath}`);
3947
4104
  });
3948
4105
  }
@@ -4057,8 +4214,8 @@ var NodeRuntime = class _NodeRuntime {
4057
4214
  }
4058
4215
  // returns the path to the downloaded zip file
4059
4216
  async downloadNodePackage(platform2, arch) {
4060
- const platformInFilename = match3(platform2).with("linux", () => "linux").with("win32", () => "win").with("darwin", () => "darwin").otherwise(() => "unknown-platform");
4061
- const archInFilename = match3(arch).with("x64", () => "x64").with("x86", () => "x86").with("arm", () => "armv7l").with("arm64", () => "arm64").otherwise(() => "unknown-arch");
4217
+ const platformInFilename = match4(platform2).with("linux", () => "linux").with("win32", () => "win").with("darwin", () => "darwin").otherwise(() => "unknown-platform");
4218
+ const archInFilename = match4(arch).with("x64", () => "x64").with("x86", () => "x86").with("arm", () => "armv7l").with("arm64", () => "arm64").otherwise(() => "unknown-arch");
4062
4219
  const packages = await this.listPackages();
4063
4220
  const url = A2(packages).find((url2) => url2.match(`node-v.*-${platformInFilename}-${archInFilename}`));
4064
4221
  if (V.isAbsent(url)) {
@@ -4122,162 +4279,7 @@ var NodeRuntime = class _NodeRuntime {
4122
4279
  }
4123
4280
  };
4124
4281
 
4125
- // src/zip.ts
4126
- import AdmZip from "adm-zip";
4127
-
4128
- // src/hash.ts
4129
- import { createHash } from "crypto";
4130
-
4131
- // src/param-map.ts
4132
- import { match as match4 } from "ts-pattern";
4133
- var ParamMap = class _ParamMap {
4134
- /**
4135
- * Parses CLI arguments of the form key:value, only supporting top-level keys.
4136
- * Only the first colon is treated as a key/value separator; all subsequent colons are part of the value.
4137
- * This means nested keys are not supported, but values can contain colons (e.g., URLs, JSON, etc.).
4138
- */
4139
- static parse(cliArguments) {
4140
- const root = new _ParamMap();
4141
- A2(cliArguments).each((arg) => {
4142
- const idx = arg.indexOf(":");
4143
- if (idx === -1) {
4144
- root.set([arg], "true");
4145
- return;
4146
- }
4147
- const key = arg.slice(0, idx);
4148
- const value = arg.slice(idx + 1);
4149
- root.set([key], value);
4150
- });
4151
- return root;
4152
- }
4153
- map;
4154
- constructor() {
4155
- this.map = /* @__PURE__ */ new Map();
4156
- }
4157
- get(keyPath) {
4158
- const keys3 = keyPath.split(":");
4159
- let node = this;
4160
- for (let i = 0; i < keys3.length; i++) {
4161
- if (!(node instanceof _ParamMap)) return void 0;
4162
- node = node.map.get(keys3[i]);
4163
- if (node === void 0) return void 0;
4164
- }
4165
- if (node instanceof _ParamMap) {
4166
- return node.toObject();
4167
- }
4168
- return this.stringToJsonObj(node);
4169
- }
4170
- set(keyPath, value) {
4171
- const keyParts = Array.isArray(keyPath) ? keyPath : keyPath.split(":");
4172
- let node = this;
4173
- for (let i = 0; i < keyParts.length - 1; i++) {
4174
- const key = keyParts[i];
4175
- let nextNode = node.map.get(key);
4176
- if (!(nextNode instanceof _ParamMap)) {
4177
- nextNode = new _ParamMap();
4178
- node.map.set(key, nextNode);
4179
- }
4180
- node = nextNode;
4181
- }
4182
- const lastKey = keyParts[keyParts.length - 1];
4183
- node.map.set(lastKey, value);
4184
- }
4185
- toObject() {
4186
- let obj = {};
4187
- M(this.map).each(([k, v]) => {
4188
- obj[k] = v instanceof _ParamMap ? v.toObject() : this.stringToJsonObj(v);
4189
- });
4190
- return obj;
4191
- }
4192
- stringToJsonObj(str) {
4193
- if (typeof str !== "string") return str;
4194
- const trimmed = str.trim();
4195
- if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
4196
- try {
4197
- return JSON.parse(trimmed);
4198
- } catch {
4199
- }
4200
- }
4201
- return match4(str).when(matches(/,/), (s) => {
4202
- return VP(s.split(",")).map((v) => v.trim()).compact([""]).map((v) => this.stringToJsonObj(v)).value;
4203
- }).when(isNumeric, (s) => parseFloat(s)).when(
4204
- (s) => s.trim() === "",
4205
- () => ""
4206
- ).otherwise(() => str);
4207
- }
4208
- };
4209
-
4210
- // src/runtime.ts
4211
- import * as z from "zod";
4212
-
4213
- // src/version.ts
4214
- var version = "0.1.47";
4215
-
4216
- // src/app.ts
4217
- import { retryUntilDefined } from "ts-retry";
4218
- import { Mutex as Mutex3 } from "async-mutex";
4219
- import { match as match5 } from "ts-pattern";
4220
-
4221
- // src/core/remote/runAllRemote.ts
4222
- var RunParamsSchema = z.object({
4223
- taskFn: z.custom((value) => typeof value === "function", {
4224
- message: "taskFn must be a function"
4225
- }),
4226
- params: z.any()
4227
- });
4228
- var RunResultSchema = z.record(z.string(), z.any());
4229
- function serializeError(value) {
4230
- if (value instanceof Error) {
4231
- const err = value;
4232
- return {
4233
- error: err.message,
4234
- name: err.name,
4235
- code: err.code,
4236
- stack: err.stack
4237
- };
4238
- }
4239
- if (value && typeof value === "object") {
4240
- return { error: JSON.stringify(value) };
4241
- }
4242
- return { error: String(value) };
4243
- }
4244
- async function run(context) {
4245
- const { params, ssh } = context;
4246
- const { taskFn, params: taskParams } = params;
4247
- const remoteResults = await ssh([], async (remoteContext) => {
4248
- const hostIdentifier = remoteContext.host?.alias || remoteContext.host?.shortName || "unknown_remote_host";
4249
- remoteContext.debug(`[${hostIdentifier}] Connected via SSH.`);
4250
- try {
4251
- const result = await remoteContext.run(taskFn(taskParams));
4252
- remoteContext.debug(`[${hostIdentifier}] Remote task result:`, JSON.stringify(result));
4253
- if (result instanceof Error) {
4254
- remoteContext.warn?.(
4255
- `[${hostIdentifier}] Remote task returned an Error instance. Converting to serializable object.`
4256
- );
4257
- return serializeError(result);
4258
- }
4259
- return result;
4260
- } catch (e) {
4261
- remoteContext.debug(`[${hostIdentifier}] Failed to run remote task:`, e.message);
4262
- return serializeError(e);
4263
- }
4264
- });
4265
- const normalizedEntries = Object.entries(remoteResults ?? {}).map(([hostAlias, value]) => {
4266
- if (value instanceof Error) {
4267
- return [hostAlias, serializeError(value)];
4268
- }
4269
- return [hostAlias, value];
4270
- });
4271
- return Object.fromEntries(normalizedEntries);
4272
- }
4273
- var runAllRemote_default = task(run, {
4274
- description: "run a task on all selected hosts",
4275
- inputSchema: RunParamsSchema,
4276
- outputSchema: RunResultSchema
4277
- });
4278
-
4279
4282
  // src/commands/pkg/package-manager.ts
4280
- import { promises as fs6 } from "fs";
4281
4283
  import filenamify from "filenamify";
4282
4284
  var PackageManager = class {
4283
4285
  constructor(app) {
@@ -6023,15 +6025,6 @@ ${successfullyUsedIdentityPaths}`);
6023
6025
  p = p.parent();
6024
6026
  }
6025
6027
  }
6026
- async printRuntimeReport() {
6027
- const nodeRuntime = new NodeRuntime(this.tmpDir);
6028
- const reportObj = {
6029
- nodePath: await nodeRuntime.nodePath(),
6030
- npmPath: await nodeRuntime.npmPath()
6031
- // summary: report?.getReport(),
6032
- };
6033
- this.info(reportObj);
6034
- }
6035
6028
  async promptPasswordInteractively(message = "Enter your password") {
6036
6029
  if (this.providedPassword !== void 0) {
6037
6030
  return this.providedPassword;
@@ -6050,11 +6043,6 @@ ${successfullyUsedIdentityPaths}`);
6050
6043
  this.providedPassword = await promptPassword({ message });
6051
6044
  return this.providedPassword;
6052
6045
  }
6053
- async installRuntime() {
6054
- this.info(`creating node runtime with ${this.tmpDir.toString()}`);
6055
- const nodeRuntime = new NodeRuntime(this.tmpDir);
6056
- await nodeRuntime.installIfNeeded();
6057
- }
6058
6046
  /**
6059
6047
  * Executes a command on the selected hosts
6060
6048
  *
@@ -6779,8 +6767,6 @@ var Cli = class {
6779
6767
  '-p, --params "<json object>"',
6780
6768
  "runtime parameters supplied as a string to be parsed as a json object representing params to supply to the script"
6781
6769
  ).option("-r, --remote", "run the script on remote hosts specified by tags via SSH orchestration").action(this.handleRun.bind(this));
6782
- const runtimeCmd = this.program.command("runtime").alias("rt").description("print out a report of the runtime environment").action(this.handleRuntime.bind(this));
6783
- runtimeCmd.command("install").alias("i").description("install a temporary runtime environment on the local host if needed").action(this.handleRuntimeInstall.bind(this));
6784
6770
  this.program.command("tasks").description("list tasks available in a package or path").argument("[package-or-path...]", "npm package spec, installed package name, or local path").option("--task <name>", "filter to a specific task name").option("--json", "output should be json formatted").action(this.handleTasksList.bind(this));
6785
6771
  }
6786
6772
  async handleInventory(options, cmd) {
@@ -6960,16 +6946,6 @@ var Cli = class {
6960
6946
  logError("hostctl error:", e);
6961
6947
  }
6962
6948
  }
6963
- async handleRuntime(options, cmd) {
6964
- const opts = cmd.optsWithGlobals();
6965
- await this.loadApp(opts);
6966
- await this.app.printRuntimeReport();
6967
- }
6968
- async handleRuntimeInstall(options, cmd) {
6969
- const opts = cmd.optsWithGlobals();
6970
- await this.loadApp(opts);
6971
- await this.app.installRuntime();
6972
- }
6973
6949
  async handlePkgCreate(packageName, options) {
6974
6950
  await createPackage(packageName, options);
6975
6951
  }