hostctl 0.1.47 → 0.1.49
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 +23 -1
- package/dist/bin/hostctl.js +348 -282
- package/dist/bin/hostctl.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +187 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/hostctl.js
CHANGED
|
@@ -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.49";
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
4061
|
-
const archInFilename =
|
|
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) {
|
|
@@ -4296,7 +4298,9 @@ var PackageManager = class {
|
|
|
4296
4298
|
this.manifest = { packages: [], version: "1.0" };
|
|
4297
4299
|
}
|
|
4298
4300
|
} catch (error) {
|
|
4299
|
-
|
|
4301
|
+
if (this.app.outputPlain()) {
|
|
4302
|
+
console.warn("Failed to load manifest, creating new one:", error);
|
|
4303
|
+
}
|
|
4300
4304
|
this.manifest = { packages: [], version: "1.0" };
|
|
4301
4305
|
}
|
|
4302
4306
|
}
|
|
@@ -4455,8 +4459,8 @@ var PackageManager = class {
|
|
|
4455
4459
|
const packageToRemove = this.findPackageToRemove(packageIdentifier);
|
|
4456
4460
|
if (!packageToRemove) {
|
|
4457
4461
|
return {
|
|
4458
|
-
success:
|
|
4459
|
-
|
|
4462
|
+
success: true,
|
|
4463
|
+
warning: `Package '${packageIdentifier}' not found. Use 'hostctl pkg list' to see installed packages.`
|
|
4460
4464
|
};
|
|
4461
4465
|
}
|
|
4462
4466
|
await this.removePackageFiles(packageToRemove);
|
|
@@ -4488,7 +4492,9 @@ var PackageManager = class {
|
|
|
4488
4492
|
prefix: packagesDir.toString()
|
|
4489
4493
|
});
|
|
4490
4494
|
} catch (error) {
|
|
4491
|
-
|
|
4495
|
+
if (this.app.outputPlain()) {
|
|
4496
|
+
console.log(`DEBUG: Error during npm uninstall: ${error}`);
|
|
4497
|
+
}
|
|
4492
4498
|
}
|
|
4493
4499
|
}
|
|
4494
4500
|
/**
|
|
@@ -4547,7 +4553,9 @@ var PackageManager = class {
|
|
|
4547
4553
|
const installDir = packagesDir.join(filenamifiedSource);
|
|
4548
4554
|
const existingPackage = this.findPackageBySource(normalizedSource);
|
|
4549
4555
|
if (existingPackage) {
|
|
4550
|
-
|
|
4556
|
+
if (this.app.outputPlain()) {
|
|
4557
|
+
console.log(`Package '${existingPackage.name}' is already installed from ${normalizedSource}`);
|
|
4558
|
+
}
|
|
4551
4559
|
return {
|
|
4552
4560
|
success: true,
|
|
4553
4561
|
packageInfo: existingPackage,
|
|
@@ -4651,7 +4659,9 @@ var PackageManager = class {
|
|
|
4651
4659
|
async findRealInstalledNpmPackagePath(packagesDir, source) {
|
|
4652
4660
|
const nodeModulesPath = packagesDir.join("node_modules");
|
|
4653
4661
|
if (!await nodeModulesPath.exists()) {
|
|
4654
|
-
|
|
4662
|
+
if (this.app.outputPlain()) {
|
|
4663
|
+
console.log(`DEBUG: node_modules does not exist at ${nodeModulesPath.toString()}`);
|
|
4664
|
+
}
|
|
4655
4665
|
return { path: null, name: null };
|
|
4656
4666
|
}
|
|
4657
4667
|
const entries = await fs6.readdir(nodeModulesPath.toString());
|
|
@@ -4692,6 +4702,9 @@ var PackageManager = class {
|
|
|
4692
4702
|
async handleDuplicateNames(finalPackageInfo) {
|
|
4693
4703
|
const actualPackageName = finalPackageInfo.name;
|
|
4694
4704
|
if (this.hasPackageWithName(actualPackageName)) {
|
|
4705
|
+
if (!this.app.outputPlain()) {
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4695
4708
|
const existingPackages = this.getPackagesWithName(actualPackageName);
|
|
4696
4709
|
console.warn(`\u26A0\uFE0F Warning: Package name '${actualPackageName}' already exists.`);
|
|
4697
4710
|
console.warn(` Existing packages:`);
|
|
@@ -6023,15 +6036,6 @@ ${successfullyUsedIdentityPaths}`);
|
|
|
6023
6036
|
p = p.parent();
|
|
6024
6037
|
}
|
|
6025
6038
|
}
|
|
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
6039
|
async promptPasswordInteractively(message = "Enter your password") {
|
|
6036
6040
|
if (this.providedPassword !== void 0) {
|
|
6037
6041
|
return this.providedPassword;
|
|
@@ -6050,11 +6054,6 @@ ${successfullyUsedIdentityPaths}`);
|
|
|
6050
6054
|
this.providedPassword = await promptPassword({ message });
|
|
6051
6055
|
return this.providedPassword;
|
|
6052
6056
|
}
|
|
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
6057
|
/**
|
|
6059
6058
|
* Executes a command on the selected hosts
|
|
6060
6059
|
*
|
|
@@ -6318,7 +6317,7 @@ dist/
|
|
|
6318
6317
|
.DS_Store
|
|
6319
6318
|
.env
|
|
6320
6319
|
`;
|
|
6321
|
-
async function createPackage(packageName, options) {
|
|
6320
|
+
async function createPackage(packageName, options, output) {
|
|
6322
6321
|
const resolvedName = expandTildePath(packageName);
|
|
6323
6322
|
const packageDir = path5.isAbsolute(resolvedName) ? resolvedName : path5.join(process.cwd(), resolvedName);
|
|
6324
6323
|
const packageSlug = path5.basename(resolvedName);
|
|
@@ -6341,115 +6340,134 @@ async function createPackage(packageName, options) {
|
|
|
6341
6340
|
await fs9.writeFile(path5.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, false));
|
|
6342
6341
|
await fs9.writeFile(path5.join(packageDir, ".gitignore"), gitignoreTemplate);
|
|
6343
6342
|
}
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
console.log(`1. cd ${packageDir}`);
|
|
6348
|
-
console.log(`2. npm install`);
|
|
6349
|
-
let step = 3;
|
|
6343
|
+
const nextSteps = [];
|
|
6344
|
+
nextSteps.push(`cd ${packageDir}`);
|
|
6345
|
+
nextSteps.push("npm install");
|
|
6350
6346
|
if (options.lang === "typescript") {
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
console.log(`${step}. npm run build`);
|
|
6354
|
-
step += 1;
|
|
6347
|
+
nextSteps.push("Edit src/index.ts and src/tasks/hello.ts to implement your tasks");
|
|
6348
|
+
nextSteps.push("npm run build");
|
|
6355
6349
|
} else {
|
|
6356
|
-
|
|
6357
|
-
step += 1;
|
|
6350
|
+
nextSteps.push("Edit src/index.js and src/tasks/hello.js to implement your tasks");
|
|
6358
6351
|
}
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6352
|
+
nextSteps.push("Test your package with: hostctl tasks .");
|
|
6353
|
+
nextSteps.push(`Run it with: hostctl run . ${registryPrefix}.hello`);
|
|
6354
|
+
if (!output?.quiet) {
|
|
6355
|
+
console.log(`Created new hostctl package '${resolvedName}' at ${packageDir}`);
|
|
6356
|
+
console.log(`
|
|
6357
|
+
Next steps:`);
|
|
6358
|
+
nextSteps.forEach((step, index) => {
|
|
6359
|
+
console.log(`${index + 1}. ${step}`);
|
|
6360
|
+
});
|
|
6361
|
+
console.log(`
|
|
6363
6362
|
Note: The package includes 'hostctl' as a dependency for local development.`);
|
|
6364
|
-
|
|
6363
|
+
console.log(`For production, you may want to add it as a peer dependency instead.`);
|
|
6364
|
+
}
|
|
6365
|
+
return {
|
|
6366
|
+
packageDir,
|
|
6367
|
+
packageName: packageJsonName,
|
|
6368
|
+
registryPrefix,
|
|
6369
|
+
language: options.lang,
|
|
6370
|
+
nextSteps
|
|
6371
|
+
};
|
|
6365
6372
|
}
|
|
6366
6373
|
|
|
6367
6374
|
// src/commands/pkg/install.ts
|
|
6368
|
-
async function installPackage(source, app) {
|
|
6375
|
+
async function installPackage(source, app, output) {
|
|
6369
6376
|
const packageManager = new PackageManager(app);
|
|
6370
6377
|
const result = await packageManager.installPackage(source);
|
|
6371
|
-
if (
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6378
|
+
if (!output?.quiet) {
|
|
6379
|
+
if (result.success) {
|
|
6380
|
+
console.log(`Installed package '${result.packageInfo.name}' from ${source} to ${result.installPath}`);
|
|
6381
|
+
} else {
|
|
6382
|
+
console.error(`Failed to install package from ${source}: ${result.error}`);
|
|
6383
|
+
}
|
|
6376
6384
|
}
|
|
6385
|
+
return { ...result, source };
|
|
6377
6386
|
}
|
|
6378
6387
|
|
|
6379
6388
|
// src/commands/pkg/list.ts
|
|
6380
6389
|
import chalk5 from "chalk";
|
|
6381
6390
|
async function listPackages(app) {
|
|
6382
6391
|
const packageManager = new PackageManager(app);
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6392
|
+
const packages = await packageManager.listPackages();
|
|
6393
|
+
const packagesByName = /* @__PURE__ */ new Map();
|
|
6394
|
+
packages.forEach((pkg) => {
|
|
6395
|
+
if (!packagesByName.has(pkg.name)) {
|
|
6396
|
+
packagesByName.set(pkg.name, []);
|
|
6388
6397
|
}
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
console.log(
|
|
6398
|
+
packagesByName.get(pkg.name).push(pkg);
|
|
6399
|
+
});
|
|
6400
|
+
const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name]) => name);
|
|
6401
|
+
return { packages, duplicateNames };
|
|
6402
|
+
}
|
|
6403
|
+
function printPackageList(packages, duplicateNames) {
|
|
6404
|
+
if (packages.length === 0) {
|
|
6405
|
+
console.log("No packages installed.");
|
|
6406
|
+
return;
|
|
6407
|
+
}
|
|
6408
|
+
const packagesByName = /* @__PURE__ */ new Map();
|
|
6409
|
+
packages.forEach((pkg) => {
|
|
6410
|
+
if (!packagesByName.has(pkg.name)) {
|
|
6411
|
+
packagesByName.set(pkg.name, []);
|
|
6412
|
+
}
|
|
6413
|
+
packagesByName.get(pkg.name).push(pkg);
|
|
6414
|
+
});
|
|
6415
|
+
console.log(`Found ${packages.length} package(s):
|
|
6397
6416
|
`);
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6417
|
+
packages.forEach((pkg) => {
|
|
6418
|
+
const packagesWithSameName = packagesByName.get(pkg.name);
|
|
6419
|
+
const hasDuplicates = packagesWithSameName.length > 1;
|
|
6420
|
+
let output = `\u2022 ${pkg.name}`;
|
|
6421
|
+
if (pkg.version) {
|
|
6422
|
+
output += ` (v${pkg.version})`;
|
|
6423
|
+
}
|
|
6424
|
+
if (pkg.description) {
|
|
6425
|
+
output += ` - ${pkg.description}`;
|
|
6426
|
+
}
|
|
6427
|
+
if (hasDuplicates) {
|
|
6428
|
+
output = chalk5.yellow(output);
|
|
6429
|
+
output += chalk5.yellow(" \u26A0\uFE0F (duplicate name)");
|
|
6430
|
+
}
|
|
6431
|
+
if (pkg.source) {
|
|
6432
|
+
if (pkg.source.startsWith("http") || pkg.source.startsWith("git@")) {
|
|
6433
|
+
output += `
|
|
6415
6434
|
\u{1F4E6} Source: ${pkg.source}`;
|
|
6416
|
-
|
|
6417
|
-
|
|
6435
|
+
} else if (!pkg.source.startsWith(".") && !pkg.source.startsWith("/")) {
|
|
6436
|
+
output += `
|
|
6418
6437
|
\u{1F4E6} Source: https://www.npmjs.com/package/${pkg.source}`;
|
|
6419
|
-
|
|
6420
|
-
|
|
6438
|
+
} else {
|
|
6439
|
+
output += `
|
|
6421
6440
|
\u{1F4E6} Source: ${pkg.source}`;
|
|
6422
|
-
}
|
|
6423
6441
|
}
|
|
6424
|
-
console.log(output);
|
|
6425
|
-
console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
|
|
6426
|
-
console.log("");
|
|
6427
|
-
});
|
|
6428
|
-
const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name, _]) => name);
|
|
6429
|
-
if (duplicateNames.length > 0) {
|
|
6430
|
-
console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
|
|
6431
|
-
duplicateNames.forEach((name) => {
|
|
6432
|
-
console.log(chalk5.yellow(` - ${name}`));
|
|
6433
|
-
});
|
|
6434
|
-
console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
|
|
6435
|
-
console.log("");
|
|
6436
6442
|
}
|
|
6437
|
-
|
|
6438
|
-
console.
|
|
6439
|
-
|
|
6443
|
+
console.log(output);
|
|
6444
|
+
console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
|
|
6445
|
+
console.log("");
|
|
6446
|
+
});
|
|
6447
|
+
if (duplicateNames.length > 0) {
|
|
6448
|
+
console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
|
|
6449
|
+
duplicateNames.forEach((name) => {
|
|
6450
|
+
console.log(chalk5.yellow(` - ${name}`));
|
|
6451
|
+
});
|
|
6452
|
+
console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
|
|
6453
|
+
console.log("");
|
|
6440
6454
|
}
|
|
6441
6455
|
}
|
|
6442
6456
|
|
|
6443
6457
|
// src/commands/pkg/remove.ts
|
|
6444
|
-
async function removePackage(packageIdentifier, app) {
|
|
6458
|
+
async function removePackage(packageIdentifier, app, output) {
|
|
6445
6459
|
const packageManager = new PackageManager(app);
|
|
6446
6460
|
const result = await packageManager.removePackage(packageIdentifier);
|
|
6447
|
-
if (
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6461
|
+
if (!output?.quiet) {
|
|
6462
|
+
if (result.success && result.packageInfo) {
|
|
6463
|
+
console.log(`Successfully removed package '${result.packageInfo.name}' (${result.packageInfo.directory})`);
|
|
6464
|
+
} else if (result.success && result.warning) {
|
|
6465
|
+
console.warn(result.warning);
|
|
6466
|
+
} else {
|
|
6467
|
+
console.error(`Failed to remove package: ${result.error}`);
|
|
6468
|
+
}
|
|
6452
6469
|
}
|
|
6470
|
+
return result;
|
|
6453
6471
|
}
|
|
6454
6472
|
|
|
6455
6473
|
// src/cli.ts
|
|
@@ -6779,8 +6797,6 @@ var Cli = class {
|
|
|
6779
6797
|
'-p, --params "<json object>"',
|
|
6780
6798
|
"runtime parameters supplied as a string to be parsed as a json object representing params to supply to the script"
|
|
6781
6799
|
).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
6800
|
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
6801
|
}
|
|
6786
6802
|
async handleInventory(options, cmd) {
|
|
@@ -6960,37 +6976,87 @@ var Cli = class {
|
|
|
6960
6976
|
logError("hostctl error:", e);
|
|
6961
6977
|
}
|
|
6962
6978
|
}
|
|
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
6979
|
async handlePkgCreate(packageName, options) {
|
|
6980
|
+
const opts = this.program.opts();
|
|
6981
|
+
if (opts.json) {
|
|
6982
|
+
try {
|
|
6983
|
+
const result = await createPackage(packageName, options, { quiet: true });
|
|
6984
|
+
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
6985
|
+
} catch (error) {
|
|
6986
|
+
console.log(
|
|
6987
|
+
JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }, null, 2)
|
|
6988
|
+
);
|
|
6989
|
+
process4.exitCode = 1;
|
|
6990
|
+
}
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
6974
6993
|
await createPackage(packageName, options);
|
|
6975
6994
|
}
|
|
6976
6995
|
async handlePkgInstall(source) {
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
console.
|
|
6982
|
-
|
|
6996
|
+
const opts = this.program.opts();
|
|
6997
|
+
await this.loadApp(opts);
|
|
6998
|
+
const result = await installPackage(source, this.app, { quiet: Boolean(opts.json) });
|
|
6999
|
+
if (opts.json) {
|
|
7000
|
+
console.log(
|
|
7001
|
+
JSON.stringify(
|
|
7002
|
+
{
|
|
7003
|
+
success: result.success,
|
|
7004
|
+
source: result.source,
|
|
7005
|
+
package: result.packageInfo,
|
|
7006
|
+
installPath: result.installPath,
|
|
7007
|
+
error: result.error
|
|
7008
|
+
},
|
|
7009
|
+
null,
|
|
7010
|
+
2
|
|
7011
|
+
)
|
|
6983
7012
|
);
|
|
7013
|
+
}
|
|
7014
|
+
if (!result.success) {
|
|
6984
7015
|
process4.exitCode = 1;
|
|
6985
7016
|
}
|
|
6986
7017
|
}
|
|
6987
7018
|
async handlePkgList() {
|
|
6988
|
-
|
|
6989
|
-
await
|
|
7019
|
+
const opts = this.program.opts();
|
|
7020
|
+
await this.loadApp(opts);
|
|
7021
|
+
const result = await listPackages(this.app);
|
|
7022
|
+
if (opts.json) {
|
|
7023
|
+
console.log(
|
|
7024
|
+
JSON.stringify(
|
|
7025
|
+
{
|
|
7026
|
+
count: result.packages.length,
|
|
7027
|
+
duplicates: result.duplicateNames,
|
|
7028
|
+
packages: result.packages
|
|
7029
|
+
},
|
|
7030
|
+
null,
|
|
7031
|
+
2
|
|
7032
|
+
)
|
|
7033
|
+
);
|
|
7034
|
+
return;
|
|
7035
|
+
}
|
|
7036
|
+
printPackageList(result.packages, result.duplicateNames);
|
|
6990
7037
|
}
|
|
6991
7038
|
async handlePkgRemove(packageIdentifier) {
|
|
6992
|
-
|
|
6993
|
-
await
|
|
7039
|
+
const opts = this.program.opts();
|
|
7040
|
+
await this.loadApp(opts);
|
|
7041
|
+
const result = await removePackage(packageIdentifier, this.app, { quiet: Boolean(opts.json) });
|
|
7042
|
+
if (opts.json) {
|
|
7043
|
+
console.log(
|
|
7044
|
+
JSON.stringify(
|
|
7045
|
+
{
|
|
7046
|
+
success: result.success,
|
|
7047
|
+
identifier: packageIdentifier,
|
|
7048
|
+
package: result.packageInfo,
|
|
7049
|
+
error: result.error,
|
|
7050
|
+
warning: result.warning
|
|
7051
|
+
},
|
|
7052
|
+
null,
|
|
7053
|
+
2
|
|
7054
|
+
)
|
|
7055
|
+
);
|
|
7056
|
+
}
|
|
7057
|
+
if (!result.success) {
|
|
7058
|
+
process4.exitCode = 1;
|
|
7059
|
+
}
|
|
6994
7060
|
}
|
|
6995
7061
|
async handleTasksList(specParts, options, cmd) {
|
|
6996
7062
|
const opts = cmd.optsWithGlobals();
|