hostctl 0.1.57 → 0.1.58
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/dist/bin/hostctl.js +290 -56
- package/dist/bin/hostctl.js.map +1 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.js +290 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/hostctl.js
CHANGED
|
@@ -2929,9 +2929,13 @@ function task(runFn4, options) {
|
|
|
2929
2929
|
options?.outputSchema
|
|
2930
2930
|
);
|
|
2931
2931
|
const taskFnObject = function(params) {
|
|
2932
|
-
|
|
2933
|
-
|
|
2932
|
+
const normalizedParams = params ?? {};
|
|
2933
|
+
const taskPartialFn = function(parentInvocation) {
|
|
2934
|
+
return parentInvocation.invokeChildTask(taskFnObject, normalizedParams);
|
|
2934
2935
|
};
|
|
2936
|
+
taskPartialFn.taskFn = taskFnObject;
|
|
2937
|
+
taskPartialFn.params = normalizedParams;
|
|
2938
|
+
return taskPartialFn;
|
|
2935
2939
|
};
|
|
2936
2940
|
Object.assign(taskFnObject, { task: taskInstance });
|
|
2937
2941
|
return taskFnObject;
|
|
@@ -3897,6 +3901,9 @@ import AdmZip from "adm-zip";
|
|
|
3897
3901
|
|
|
3898
3902
|
// src/hash.ts
|
|
3899
3903
|
import { createHash } from "crypto";
|
|
3904
|
+
function sha256(str) {
|
|
3905
|
+
return createHash("sha256").update(str).digest("hex");
|
|
3906
|
+
}
|
|
3900
3907
|
|
|
3901
3908
|
// src/param-map.ts
|
|
3902
3909
|
import { match as match2 } from "ts-pattern";
|
|
@@ -3981,7 +3988,7 @@ var ParamMap = class _ParamMap {
|
|
|
3981
3988
|
import * as z from "zod";
|
|
3982
3989
|
|
|
3983
3990
|
// src/version.ts
|
|
3984
|
-
var version = "0.1.
|
|
3991
|
+
var version = "0.1.58";
|
|
3985
3992
|
|
|
3986
3993
|
// src/app.ts
|
|
3987
3994
|
import { retryUntilDefined } from "ts-retry";
|
|
@@ -6731,58 +6738,64 @@ async function detectRockyLinux(exec) {
|
|
|
6731
6738
|
}
|
|
6732
6739
|
var os_default = task(
|
|
6733
6740
|
async function run27(context) {
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
variant
|
|
6741
|
+
return await context.memoize(
|
|
6742
|
+
"core.host.os:v1",
|
|
6743
|
+
async () => {
|
|
6744
|
+
try {
|
|
6745
|
+
const { exec } = context;
|
|
6746
|
+
const {
|
|
6747
|
+
success: ostypeSuccess,
|
|
6748
|
+
stdout: ostypeOutput,
|
|
6749
|
+
stderr: ostypeStderr
|
|
6750
|
+
} = await exec(["bash", "-c", "echo $OSTYPE"]);
|
|
6751
|
+
if (!ostypeSuccess) {
|
|
6752
|
+
throw new Error(`Failed to get OSTYPE: ${ostypeStderr}`);
|
|
6753
|
+
}
|
|
6754
|
+
const family = await match5(ostypeOutput.trim().toLowerCase()).with(P3.string.startsWith("solaris"), () => "solaris").with(P3.string.startsWith("darwin"), () => "darwin").with(P3.string.startsWith("linux"), () => "linux").with(P3.string.startsWith("bsd"), () => "bsd").with(P3.string.startsWith("freebsd"), () => "bsd").with(P3.string.startsWith("msys"), () => "windows").with(P3.string.startsWith("cygwin"), () => "windows").with(P3.string.startsWith("mingw"), () => "windows").otherwise(async () => {
|
|
6755
|
+
const { stdout: unameOutput } = await exec(["uname"]);
|
|
6756
|
+
const unameFamily = match5(unameOutput.trim().toLowerCase()).with(P3.string.startsWith("sunos"), () => "solaris").with(P3.string.startsWith("darwin"), () => "darwin").with(P3.string.startsWith("linux"), () => "linux").with(P3.string.startsWith("freebsd"), () => "bsd").with(P3.string.startsWith("openbsd"), () => "bsd").with(P3.string.startsWith("netbsd"), () => "bsd").otherwise(() => "unknown");
|
|
6757
|
+
return unameFamily;
|
|
6758
|
+
});
|
|
6759
|
+
const [osIdLike, osId, osVersion] = await match5(family).with("bsd", async () => {
|
|
6760
|
+
const { stdout: unameROutput } = await exec(["uname", "-r"]);
|
|
6761
|
+
return [family, family, unameROutput.trim()];
|
|
6762
|
+
}).with("darwin", async () => {
|
|
6763
|
+
const { stdout: swVersOutput } = await exec(["sw_vers", "-productVersion"]);
|
|
6764
|
+
return [family, family, swVersOutput.trim()];
|
|
6765
|
+
}).with("linux", async () => {
|
|
6766
|
+
const { idLike, id, version: version2 } = await getOsReleaseInfo(exec);
|
|
6767
|
+
return [idLike, id, version2];
|
|
6768
|
+
}).with("solaris", async () => {
|
|
6769
|
+
const { stdout: unameROutput } = await exec(["uname", "-r"]);
|
|
6770
|
+
return ["solaris", "solaris", unameROutput.trim()];
|
|
6771
|
+
}).with("windows", () => ["windows", "windows", "unknown"]).otherwise(() => ["unknown", "unknown", "unknown"]);
|
|
6772
|
+
let variant = osId.toLowerCase();
|
|
6773
|
+
if (family === "linux" && !variant.includes("rocky") && (osIdLike.toLowerCase().includes("rocky") || osId.toLowerCase().includes("rhel"))) {
|
|
6774
|
+
const isRocky = await detectRockyLinux(exec);
|
|
6775
|
+
if (isRocky) {
|
|
6776
|
+
variant = "rocky";
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6779
|
+
return {
|
|
6780
|
+
success: true,
|
|
6781
|
+
family,
|
|
6782
|
+
os: osIdLike.toLowerCase(),
|
|
6783
|
+
variant,
|
|
6784
|
+
version: osVersion
|
|
6785
|
+
};
|
|
6786
|
+
} catch (error) {
|
|
6787
|
+
return {
|
|
6788
|
+
success: false,
|
|
6789
|
+
error: error.message,
|
|
6790
|
+
family: "unknown",
|
|
6791
|
+
os: "unknown",
|
|
6792
|
+
variant: "unknown",
|
|
6793
|
+
version: "unknown"
|
|
6794
|
+
};
|
|
6767
6795
|
}
|
|
6768
|
-
}
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
family,
|
|
6772
|
-
os: osIdLike.toLowerCase(),
|
|
6773
|
-
variant,
|
|
6774
|
-
version: osVersion
|
|
6775
|
-
};
|
|
6776
|
-
} catch (error) {
|
|
6777
|
-
return {
|
|
6778
|
-
success: false,
|
|
6779
|
-
error: error.message,
|
|
6780
|
-
family: "unknown",
|
|
6781
|
-
os: "unknown",
|
|
6782
|
-
variant: "unknown",
|
|
6783
|
-
version: "unknown"
|
|
6784
|
-
};
|
|
6785
|
-
}
|
|
6796
|
+
},
|
|
6797
|
+
{ scope: "host" }
|
|
6798
|
+
);
|
|
6786
6799
|
},
|
|
6787
6800
|
{
|
|
6788
6801
|
name: "os",
|
|
@@ -31791,6 +31804,154 @@ function registrySize(registry2) {
|
|
|
31791
31804
|
return registry2.tasks().length;
|
|
31792
31805
|
}
|
|
31793
31806
|
|
|
31807
|
+
// src/task-cache.ts
|
|
31808
|
+
var DEFAULT_CACHE_MODE = "use";
|
|
31809
|
+
var LOCAL_HOST_SCOPE_KEY = "host:local";
|
|
31810
|
+
function normalizeValue(value) {
|
|
31811
|
+
if (value === void 0) {
|
|
31812
|
+
return void 0;
|
|
31813
|
+
}
|
|
31814
|
+
if (value === null) {
|
|
31815
|
+
return null;
|
|
31816
|
+
}
|
|
31817
|
+
if (typeof value === "bigint") {
|
|
31818
|
+
return value.toString();
|
|
31819
|
+
}
|
|
31820
|
+
if (value instanceof Date) {
|
|
31821
|
+
return value.toISOString();
|
|
31822
|
+
}
|
|
31823
|
+
if (Array.isArray(value)) {
|
|
31824
|
+
return value.map((item) => {
|
|
31825
|
+
const normalized = normalizeValue(item);
|
|
31826
|
+
return normalized === void 0 ? null : normalized;
|
|
31827
|
+
});
|
|
31828
|
+
}
|
|
31829
|
+
if (typeof value === "object") {
|
|
31830
|
+
const obj = value;
|
|
31831
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
31832
|
+
const result = {};
|
|
31833
|
+
for (const key of sortedKeys) {
|
|
31834
|
+
const normalized = normalizeValue(obj[key]);
|
|
31835
|
+
if (normalized !== void 0) {
|
|
31836
|
+
result[key] = normalized;
|
|
31837
|
+
}
|
|
31838
|
+
}
|
|
31839
|
+
return result;
|
|
31840
|
+
}
|
|
31841
|
+
return value;
|
|
31842
|
+
}
|
|
31843
|
+
function stableJson(value) {
|
|
31844
|
+
const normalized = normalizeValue(value);
|
|
31845
|
+
return JSON.stringify(normalized ?? null);
|
|
31846
|
+
}
|
|
31847
|
+
function normalizeCacheConfig(cache) {
|
|
31848
|
+
if (!cache) {
|
|
31849
|
+
return { enabled: false, mode: DEFAULT_CACHE_MODE };
|
|
31850
|
+
}
|
|
31851
|
+
if (cache === true) {
|
|
31852
|
+
return { enabled: true, mode: DEFAULT_CACHE_MODE };
|
|
31853
|
+
}
|
|
31854
|
+
if (typeof cache === "string") {
|
|
31855
|
+
return { enabled: true, scope: cache, mode: DEFAULT_CACHE_MODE };
|
|
31856
|
+
}
|
|
31857
|
+
return {
|
|
31858
|
+
enabled: true,
|
|
31859
|
+
scope: cache.scope,
|
|
31860
|
+
key: cache.key,
|
|
31861
|
+
ttlMs: cache.ttlMs,
|
|
31862
|
+
mode: cache.mode ?? DEFAULT_CACHE_MODE
|
|
31863
|
+
};
|
|
31864
|
+
}
|
|
31865
|
+
function buildTaskCacheKey(taskIdentity, params) {
|
|
31866
|
+
const taskName = taskIdentity?.task?.name;
|
|
31867
|
+
const modulePath = taskIdentity?.task?.taskModuleAbsolutePath;
|
|
31868
|
+
const identity2 = taskName ?? (modulePath ? Path.new(modulePath).absolute().toString() : "unknown-task");
|
|
31869
|
+
const paramsHash = sha256(stableJson(params ?? {}));
|
|
31870
|
+
return `${identity2}:${paramsHash}`;
|
|
31871
|
+
}
|
|
31872
|
+
function buildHostScopeKey(host, configRef) {
|
|
31873
|
+
if (!host) {
|
|
31874
|
+
return LOCAL_HOST_SCOPE_KEY;
|
|
31875
|
+
}
|
|
31876
|
+
const identity2 = {
|
|
31877
|
+
alias: host.alias ?? "",
|
|
31878
|
+
hostname: host.hostname ?? "",
|
|
31879
|
+
user: host.user ?? "",
|
|
31880
|
+
port: host.port ?? 22,
|
|
31881
|
+
config: configRef ?? ""
|
|
31882
|
+
};
|
|
31883
|
+
return `host:${sha256(stableJson(identity2))}`;
|
|
31884
|
+
}
|
|
31885
|
+
function buildInvocationScopeKey(rootInvocationId) {
|
|
31886
|
+
return `invocation:${rootInvocationId}`;
|
|
31887
|
+
}
|
|
31888
|
+
var TaskCacheStore = class {
|
|
31889
|
+
entries = /* @__PURE__ */ new Map();
|
|
31890
|
+
runId;
|
|
31891
|
+
constructor(runId) {
|
|
31892
|
+
this.runId = runId ?? crypto.randomUUID();
|
|
31893
|
+
}
|
|
31894
|
+
globalScopeKey() {
|
|
31895
|
+
return `global:${this.runId}`;
|
|
31896
|
+
}
|
|
31897
|
+
get(scopeKey, cacheKey2) {
|
|
31898
|
+
const scope = this.entries.get(scopeKey);
|
|
31899
|
+
if (!scope) {
|
|
31900
|
+
return void 0;
|
|
31901
|
+
}
|
|
31902
|
+
const entry = scope.get(cacheKey2);
|
|
31903
|
+
if (!entry) {
|
|
31904
|
+
return void 0;
|
|
31905
|
+
}
|
|
31906
|
+
if (entry.expiresAt > 0 && Date.now() > entry.expiresAt) {
|
|
31907
|
+
scope.delete(cacheKey2);
|
|
31908
|
+
if (scope.size === 0) {
|
|
31909
|
+
this.entries.delete(scopeKey);
|
|
31910
|
+
}
|
|
31911
|
+
return void 0;
|
|
31912
|
+
}
|
|
31913
|
+
return entry;
|
|
31914
|
+
}
|
|
31915
|
+
set(scopeKey, cacheKey2, value, ttlMs) {
|
|
31916
|
+
const scope = this.entries.get(scopeKey) ?? /* @__PURE__ */ new Map();
|
|
31917
|
+
const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : 0;
|
|
31918
|
+
const entry = { value, expiresAt };
|
|
31919
|
+
scope.set(cacheKey2, entry);
|
|
31920
|
+
this.entries.set(scopeKey, scope);
|
|
31921
|
+
return entry;
|
|
31922
|
+
}
|
|
31923
|
+
delete(scopeKey, cacheKey2) {
|
|
31924
|
+
const scope = this.entries.get(scopeKey);
|
|
31925
|
+
if (!scope) {
|
|
31926
|
+
return;
|
|
31927
|
+
}
|
|
31928
|
+
scope.delete(cacheKey2);
|
|
31929
|
+
if (scope.size === 0) {
|
|
31930
|
+
this.entries.delete(scopeKey);
|
|
31931
|
+
}
|
|
31932
|
+
}
|
|
31933
|
+
async resolve(scopeKey, cacheKey2, compute, options = {}) {
|
|
31934
|
+
const mode = options.mode ?? DEFAULT_CACHE_MODE;
|
|
31935
|
+
if (mode === "bypass") {
|
|
31936
|
+
return await compute();
|
|
31937
|
+
}
|
|
31938
|
+
if (mode === "use") {
|
|
31939
|
+
const cached = this.get(scopeKey, cacheKey2);
|
|
31940
|
+
if (cached) {
|
|
31941
|
+
return await cached.value;
|
|
31942
|
+
}
|
|
31943
|
+
}
|
|
31944
|
+
const promise = Promise.resolve().then(compute);
|
|
31945
|
+
this.set(scopeKey, cacheKey2, promise, options.ttlMs);
|
|
31946
|
+
try {
|
|
31947
|
+
return await promise;
|
|
31948
|
+
} catch (error) {
|
|
31949
|
+
this.delete(scopeKey, cacheKey2);
|
|
31950
|
+
throw error;
|
|
31951
|
+
}
|
|
31952
|
+
}
|
|
31953
|
+
};
|
|
31954
|
+
|
|
31794
31955
|
// src/app.ts
|
|
31795
31956
|
var TaskTree = class {
|
|
31796
31957
|
// private taskEventBus: Emittery<{ newTask: NewTaskEvent; taskComplete: TaskCompleteEvent }>;
|
|
@@ -31887,6 +32048,7 @@ var App3 = class _App {
|
|
|
31887
32048
|
outputStyle;
|
|
31888
32049
|
_tmpDir;
|
|
31889
32050
|
tmpFileRegistry;
|
|
32051
|
+
taskCache;
|
|
31890
32052
|
taskTree;
|
|
31891
32053
|
verbosity = Verbosity.ERROR;
|
|
31892
32054
|
passwordProvider;
|
|
@@ -31896,6 +32058,7 @@ var App3 = class _App {
|
|
|
31896
32058
|
this.taskTree = new TaskTree();
|
|
31897
32059
|
this.outputStyle = "plain";
|
|
31898
32060
|
this.tmpFileRegistry = new TmpFileRegistry(this.hostctlTmpDir());
|
|
32061
|
+
this.taskCache = new TaskCacheStore();
|
|
31899
32062
|
this.configRef = void 0;
|
|
31900
32063
|
this.hostSelector = void 0;
|
|
31901
32064
|
process3.on("exit", (code) => this.appExitCallback());
|
|
@@ -32211,6 +32374,27 @@ ${cmdRes.stderr.trim()}`));
|
|
|
32211
32374
|
}
|
|
32212
32375
|
taskContextForRunFn(invocation, params, hostForContext) {
|
|
32213
32376
|
const effectiveHost = hostForContext || invocation.host;
|
|
32377
|
+
const rootInvocationId = (() => {
|
|
32378
|
+
let current = invocation;
|
|
32379
|
+
while (current.parent) {
|
|
32380
|
+
current = current.parent;
|
|
32381
|
+
}
|
|
32382
|
+
return current.id;
|
|
32383
|
+
})();
|
|
32384
|
+
const defaultScope = () => effectiveHost ? "host" : "global";
|
|
32385
|
+
const scopeKeyFor = (scope) => {
|
|
32386
|
+
switch (scope) {
|
|
32387
|
+
case "global":
|
|
32388
|
+
return this.taskCache.globalScopeKey();
|
|
32389
|
+
case "host":
|
|
32390
|
+
return buildHostScopeKey(effectiveHost, this.configRef);
|
|
32391
|
+
case "invocation":
|
|
32392
|
+
return buildInvocationScopeKey(rootInvocationId);
|
|
32393
|
+
}
|
|
32394
|
+
};
|
|
32395
|
+
const isTaskFn = (candidate) => {
|
|
32396
|
+
return typeof candidate === "function" && !!candidate.task;
|
|
32397
|
+
};
|
|
32214
32398
|
return {
|
|
32215
32399
|
// Properties from TaskContext
|
|
32216
32400
|
params,
|
|
@@ -32240,8 +32424,58 @@ ${cmdRes.stderr.trim()}`));
|
|
|
32240
32424
|
ssh: async (tags, remoteTaskFn) => {
|
|
32241
32425
|
return await invocation.ssh(tags, remoteTaskFn);
|
|
32242
32426
|
},
|
|
32243
|
-
run: async (
|
|
32244
|
-
|
|
32427
|
+
run: async (...args) => {
|
|
32428
|
+
const [firstArg, secondArg, thirdArg] = args;
|
|
32429
|
+
let taskPartialFn;
|
|
32430
|
+
let taskIdentity;
|
|
32431
|
+
let paramsForKey;
|
|
32432
|
+
let options;
|
|
32433
|
+
if (isTaskFn(firstArg)) {
|
|
32434
|
+
taskIdentity = firstArg;
|
|
32435
|
+
paramsForKey = secondArg ?? {};
|
|
32436
|
+
options = thirdArg;
|
|
32437
|
+
taskPartialFn = taskIdentity(paramsForKey);
|
|
32438
|
+
} else {
|
|
32439
|
+
taskPartialFn = firstArg;
|
|
32440
|
+
options = secondArg;
|
|
32441
|
+
const meta = taskPartialFn;
|
|
32442
|
+
taskIdentity = meta.taskFn;
|
|
32443
|
+
paramsForKey = meta.params;
|
|
32444
|
+
}
|
|
32445
|
+
const cacheDecision = normalizeCacheConfig(options?.cache);
|
|
32446
|
+
if (!cacheDecision.enabled || cacheDecision.mode === "bypass") {
|
|
32447
|
+
return await invocation.run(taskPartialFn);
|
|
32448
|
+
}
|
|
32449
|
+
const scope = cacheDecision.scope ?? defaultScope();
|
|
32450
|
+
const cacheKey2 = cacheDecision.key ?? buildTaskCacheKey(taskIdentity, paramsForKey ?? {});
|
|
32451
|
+
const scopeKey = scopeKeyFor(scope);
|
|
32452
|
+
return await this.taskCache.resolve(scopeKey, cacheKey2, () => invocation.run(taskPartialFn), {
|
|
32453
|
+
ttlMs: cacheDecision.ttlMs,
|
|
32454
|
+
mode: cacheDecision.mode
|
|
32455
|
+
});
|
|
32456
|
+
},
|
|
32457
|
+
memoize: async (key, valueOrFactory, options) => {
|
|
32458
|
+
const cacheDecision = normalizeCacheConfig({
|
|
32459
|
+
scope: options?.scope,
|
|
32460
|
+
ttlMs: options?.ttlMs,
|
|
32461
|
+
mode: options?.mode,
|
|
32462
|
+
key
|
|
32463
|
+
});
|
|
32464
|
+
const scope = cacheDecision.scope ?? defaultScope();
|
|
32465
|
+
const scopeKey = scopeKeyFor(scope);
|
|
32466
|
+
const compute = async () => {
|
|
32467
|
+
if (typeof valueOrFactory === "function") {
|
|
32468
|
+
return await valueOrFactory();
|
|
32469
|
+
}
|
|
32470
|
+
return await valueOrFactory;
|
|
32471
|
+
};
|
|
32472
|
+
if (cacheDecision.mode === "bypass") {
|
|
32473
|
+
return await compute();
|
|
32474
|
+
}
|
|
32475
|
+
return await this.taskCache.resolve(scopeKey, key, compute, {
|
|
32476
|
+
ttlMs: cacheDecision.ttlMs,
|
|
32477
|
+
mode: cacheDecision.mode
|
|
32478
|
+
});
|
|
32245
32479
|
},
|
|
32246
32480
|
getPassword: async () => {
|
|
32247
32481
|
return await invocation.getPassword();
|