hostctl 0.1.32

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.
@@ -0,0 +1,4962 @@
1
+ #!/usr/bin/env -S npx tsx
2
+
3
+ // src/cli.ts
4
+ import process4 from "process";
5
+ import * as cmdr from "commander";
6
+
7
+ // src/app.ts
8
+ import process3 from "process";
9
+ import * as fs6 from "fs";
10
+ import { homedir as homedir3 } from "os";
11
+
12
+ // src/handlebars.ts
13
+ import Handlebars from "handlebars";
14
+ Handlebars.registerHelper("join", function(items, block) {
15
+ var delimiter = block.hash.delimiter || ",", start = block.hash.start || 0, len = items ? items.length : 0, end = block.hash.end || len, out = "";
16
+ if (end > len) end = len;
17
+ if ("function" === typeof block) {
18
+ for (let i = start; i < end; i++) {
19
+ if (i > start) out += delimiter;
20
+ if ("string" === typeof items[i]) out += items[i];
21
+ else out += block(items[i]);
22
+ }
23
+ return out;
24
+ } else {
25
+ return [].concat(items).slice(start, end).join(delimiter);
26
+ }
27
+ });
28
+
29
+ // src/config.ts
30
+ import * as fs4 from "fs";
31
+
32
+ // src/config-file.ts
33
+ import fsa from "fs/promises";
34
+ import "fs";
35
+ import { globSync as globSync2 } from "glob";
36
+ import { bech32 } from "@scure/base";
37
+ import "rambda";
38
+ import yaml from "js-yaml";
39
+
40
+ // src/flex/function.ts
41
+ var GeneratorFunction = function* () {
42
+ }.constructor;
43
+ var MethodBag = class {
44
+ methods;
45
+ staticMethods;
46
+ constructor() {
47
+ this.methods = /* @__PURE__ */ new Map();
48
+ this.staticMethods = /* @__PURE__ */ new Map();
49
+ }
50
+ registerUncurriedFns(fnBag) {
51
+ Object.entries(fnBag).forEach(([functionName, functionObj]) => {
52
+ this.methods.set(functionName, [functionObj, 1]);
53
+ });
54
+ }
55
+ registerCurriedFns(fnBag) {
56
+ Object.entries(fnBag).forEach(([functionName, functionObj]) => {
57
+ this.methods.set(functionName, [functionObj, 2]);
58
+ });
59
+ }
60
+ registerStatic(fnBag) {
61
+ Object.entries(fnBag).forEach(([functionName, functionObj]) => {
62
+ this.staticMethods.set(functionName, functionObj);
63
+ });
64
+ }
65
+ lookup(fnName) {
66
+ const pair = this.methods.get(fnName);
67
+ if (!pair) {
68
+ return void 0;
69
+ }
70
+ return pair;
71
+ }
72
+ lookupStatic(fnName) {
73
+ const fn = this.staticMethods.get(fnName);
74
+ if (!fn) {
75
+ return void 0;
76
+ }
77
+ return fn;
78
+ }
79
+ };
80
+ function buildWrapperProxy() {
81
+ const methods = new MethodBag();
82
+ function func(wrappedValue) {
83
+ return new Proxy(wrappedValue, {
84
+ get(target, property, receiver) {
85
+ const fnArityPair = methods.lookup(property.toString());
86
+ if (fnArityPair === void 0) {
87
+ return Reflect.get(target, property, receiver);
88
+ }
89
+ const [fn, arity] = fnArityPair;
90
+ return function(...args) {
91
+ if (arity === 1) {
92
+ return fn.call(receiver, target);
93
+ } else {
94
+ return fn.apply(receiver, args)(target);
95
+ }
96
+ };
97
+ }
98
+ });
99
+ }
100
+ Object.assign(func, {
101
+ registerUncurriedFns: (fnBag) => {
102
+ methods.registerUncurriedFns(fnBag);
103
+ },
104
+ registerCurriedFns: (fnBag) => {
105
+ methods.registerCurriedFns(fnBag);
106
+ },
107
+ registerStatic: (fnBag) => {
108
+ methods.registerStatic(fnBag);
109
+ }
110
+ });
111
+ const funcWithMethods = func;
112
+ return new Proxy(funcWithMethods, {
113
+ get(target, property, receiver) {
114
+ const fn = methods.lookupStatic(property.toString());
115
+ if (fn === void 0) {
116
+ return Reflect.get(target, property, receiver);
117
+ }
118
+ return fn;
119
+ }
120
+ });
121
+ }
122
+ function curry(func) {
123
+ return function curried(...args) {
124
+ if (args.length >= func.length) {
125
+ return func.apply(this, args);
126
+ } else {
127
+ return function(...args2) {
128
+ return curried.apply(this, args.concat(args2));
129
+ };
130
+ }
131
+ };
132
+ }
133
+ function identity(val) {
134
+ return val;
135
+ }
136
+ function parameters(func) {
137
+ const ARROW = true;
138
+ const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
139
+ const FUNC_ARG_SPLIT = /,/;
140
+ const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
141
+ const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
142
+ return ((func || "").toString().replace(STRIP_COMMENTS, "").match(FUNC_ARGS) || ["", "", ""])[2].split(FUNC_ARG_SPLIT).map(function(arg) {
143
+ return arg.replace(FUNC_ARG, function(all, underscore, name) {
144
+ return name.split("=")[0].trim();
145
+ });
146
+ }).filter(String);
147
+ }
148
+ function thread(...args) {
149
+ return function(firstArg = void 0) {
150
+ let pipeline = args;
151
+ if (firstArg !== void 0) {
152
+ pipeline.unshift(firstArg);
153
+ }
154
+ return pipeline.reduce((arg, fn) => fn(arg));
155
+ };
156
+ }
157
+ function threadR(...fns) {
158
+ return function(firstArg = void 0) {
159
+ let pipeline = fns;
160
+ if (firstArg !== void 0) {
161
+ pipeline.push(firstArg);
162
+ }
163
+ return pipeline.reduceRight((arg, fn) => fn(arg));
164
+ };
165
+ }
166
+ var F = buildWrapperProxy();
167
+ F.registerUncurriedFns({ parameters });
168
+ F.registerStatic({
169
+ curry,
170
+ identity,
171
+ parameters,
172
+ thread,
173
+ threadR
174
+ });
175
+
176
+ // src/flex/protocol.ts
177
+ import { Mutex } from "async-mutex";
178
+
179
+ // src/flex/type.ts
180
+ import { inspect as nodeInspect } from "util";
181
+ var End = class _End {
182
+ static _instance;
183
+ constructor() {
184
+ }
185
+ static get instance() {
186
+ if (!this._instance) {
187
+ this._instance = new _End();
188
+ }
189
+ return this._instance;
190
+ }
191
+ };
192
+ var Undefined = class {
193
+ };
194
+ var Null = class {
195
+ };
196
+ var Class = class {
197
+ };
198
+ function inspect(valueToInspect) {
199
+ return nodeInspect(valueToInspect, { showProxy: true });
200
+ }
201
+ function isA(predicateType) {
202
+ return function(value) {
203
+ return value instanceof predicateType || kind(value) === predicateType || value != null && (value.constructor === predicateType || predicateType.name === "Object" && typeof value === "object");
204
+ };
205
+ }
206
+ function isAbsent(value) {
207
+ return value === null || value === void 0;
208
+ }
209
+ function isDefined(value) {
210
+ return value !== void 0;
211
+ }
212
+ var BUILT_IN_CONSTRUCTORS = [
213
+ String,
214
+ Number,
215
+ Boolean,
216
+ Array,
217
+ Object,
218
+ Function,
219
+ Date,
220
+ RegExp,
221
+ Map,
222
+ Set,
223
+ Error,
224
+ Promise,
225
+ Symbol,
226
+ BigInt
227
+ ];
228
+ function isClass(fn) {
229
+ if (typeof fn !== "function") {
230
+ return false;
231
+ }
232
+ if (/^class\s/.test(Function.prototype.toString.call(fn))) {
233
+ return true;
234
+ }
235
+ if (BUILT_IN_CONSTRUCTORS.includes(fn)) {
236
+ return true;
237
+ }
238
+ return false;
239
+ }
240
+ function isObject(val) {
241
+ return val !== null && (typeof val === "function" || typeof val === "object");
242
+ }
243
+ function isPresent(value) {
244
+ return !isAbsent(value);
245
+ }
246
+ function kind(value) {
247
+ if (value === null) {
248
+ return Null;
249
+ }
250
+ switch (typeof value) {
251
+ case "undefined":
252
+ return Undefined;
253
+ case "boolean":
254
+ return Boolean;
255
+ case "number":
256
+ return Number;
257
+ case "bigint":
258
+ return BigInt;
259
+ case "string":
260
+ return String;
261
+ case "symbol":
262
+ return Symbol;
263
+ case "function":
264
+ if (isClass(value)) {
265
+ return Class;
266
+ } else {
267
+ return value.constructor;
268
+ }
269
+ break;
270
+ case "object":
271
+ return value.constructor;
272
+ break;
273
+ }
274
+ }
275
+ function klass(objOrClass) {
276
+ if (isClass(objOrClass)) {
277
+ return Function;
278
+ }
279
+ const proto = Object.getPrototypeOf(objOrClass);
280
+ if (proto === null || proto === void 0) {
281
+ return void 0;
282
+ }
283
+ return proto.constructor;
284
+ }
285
+ function superclass(objOrClass) {
286
+ if (objOrClass === Object) {
287
+ return null;
288
+ }
289
+ if (isClass(objOrClass)) {
290
+ const proto = Object.getPrototypeOf(objOrClass);
291
+ if (proto === Function.prototype) {
292
+ return Object;
293
+ }
294
+ return proto;
295
+ } else {
296
+ const instanceProto = Object.getPrototypeOf(objOrClass);
297
+ if (instanceProto === null || instanceProto === void 0) {
298
+ return void 0;
299
+ }
300
+ const superProto = Object.getPrototypeOf(instanceProto);
301
+ if (superProto === null || superProto === void 0) {
302
+ return superProto === null ? null : void 0;
303
+ }
304
+ return superProto.constructor;
305
+ }
306
+ }
307
+ var Value = class {
308
+ constructor(value) {
309
+ this.value = value;
310
+ }
311
+ isWrappedValue = true;
312
+ // isA(predicateType: any): boolean {
313
+ // return isA(predicateType)(this.value);
314
+ // }
315
+ // isAbsent(value: any) {
316
+ // return isAbsent(value);
317
+ // }
318
+ // isClass(value: unknown) {
319
+ // isClass(value);
320
+ // }
321
+ // isPresent(value: any) {
322
+ // return isPresent(value);
323
+ // }
324
+ // isError(value: unknown): value is Error {
325
+ // return isError(value);
326
+ // }
327
+ // inspect() {
328
+ // return inspect(this.value);
329
+ // }
330
+ // kind() {
331
+ // return kind(this.value);
332
+ // }
333
+ // klass() {
334
+ // return klass(this.value);
335
+ // }
336
+ // superclass() {
337
+ // return superclass(this.value);
338
+ // }
339
+ };
340
+ function buildValueWrapperProxy() {
341
+ const methods = new MethodBag();
342
+ function func(wrappedValue) {
343
+ if (!isObject(wrappedValue)) {
344
+ wrappedValue = new Value(wrappedValue);
345
+ }
346
+ return new Proxy(wrappedValue, {
347
+ get(target, property, receiver) {
348
+ const fnArityPair = methods.lookup(property.toString());
349
+ if (fnArityPair === void 0) {
350
+ if (target.isWrappedValue) {
351
+ return Reflect.get(target.value, property, receiver);
352
+ } else {
353
+ return Reflect.get(target, property, receiver);
354
+ }
355
+ }
356
+ const [fn, arity] = fnArityPair;
357
+ return function(...args) {
358
+ if (arity === 1) {
359
+ if (target.isWrappedValue) {
360
+ return fn.call(receiver, target.value);
361
+ } else {
362
+ return fn.call(receiver, target);
363
+ }
364
+ } else {
365
+ if (target.isWrappedValue) {
366
+ return fn.apply(receiver, args)(target.value);
367
+ } else {
368
+ return fn.apply(receiver, args)(target);
369
+ }
370
+ }
371
+ };
372
+ }
373
+ });
374
+ }
375
+ Object.assign(func, {
376
+ registerUncurriedFns(fnBag) {
377
+ return methods.registerUncurriedFns(fnBag);
378
+ },
379
+ registerCurriedFns(fnBag) {
380
+ return methods.registerCurriedFns(fnBag);
381
+ },
382
+ registerStatic(fnBag) {
383
+ return methods.registerStatic(fnBag);
384
+ }
385
+ });
386
+ const funcWithMethods = func;
387
+ return new Proxy(funcWithMethods, {
388
+ get(target, property, receiver) {
389
+ const fn = methods.lookupStatic(property.toString());
390
+ if (fn === void 0) {
391
+ return Reflect.get(target, property, receiver);
392
+ }
393
+ return fn;
394
+ }
395
+ });
396
+ }
397
+ var V = buildValueWrapperProxy();
398
+ V.registerUncurriedFns({
399
+ isAbsent,
400
+ isClass,
401
+ isDefined,
402
+ isObject,
403
+ isPresent,
404
+ inspect,
405
+ kind,
406
+ klass,
407
+ superclass
408
+ });
409
+ V.registerCurriedFns({ isA });
410
+ V.registerStatic({
411
+ isA,
412
+ isAbsent,
413
+ isClass,
414
+ isDefined,
415
+ isObject,
416
+ isPresent,
417
+ inspect,
418
+ kind,
419
+ klass,
420
+ superclass
421
+ });
422
+
423
+ // src/flex/protocol.ts
424
+ var NamedImplementations = class {
425
+ constructor(impls = /* @__PURE__ */ new Map(), defaultImpl = "") {
426
+ this.impls = impls;
427
+ this.defaultImpl = defaultImpl;
428
+ }
429
+ register(name, implClass, makeDefault = false) {
430
+ if (this.impls.size === 0 || makeDefault) {
431
+ this.setDefaultImpl(name);
432
+ }
433
+ this.impls.set(name, implClass);
434
+ }
435
+ get(name) {
436
+ return this.impls.get(name || this.defaultImpl);
437
+ }
438
+ setDefaultImpl(name) {
439
+ this.defaultImpl = name;
440
+ }
441
+ };
442
+ var ImplementationRegistry = class {
443
+ constructor(name) {
444
+ this.name = name;
445
+ }
446
+ // implementations is a Map of (type constructor function => ProtocolImplGroup) pairs
447
+ // It stores all the named implementations of the typeclass/protocol associated with the type stored in the key of the map
448
+ implementations = /* @__PURE__ */ new Map();
449
+ register(classConstructor, implClass, makeDefault = false) {
450
+ let protocolImplGroup = this.implementations.get(classConstructor);
451
+ if (!protocolImplGroup) {
452
+ protocolImplGroup = new NamedImplementations();
453
+ this.implementations.set(classConstructor, protocolImplGroup);
454
+ }
455
+ protocolImplGroup.register(implClass.name, implClass, makeDefault);
456
+ }
457
+ use(classConstructor, implClass) {
458
+ this.register(classConstructor, implClass, true);
459
+ }
460
+ clear() {
461
+ this.implementations.clear();
462
+ }
463
+ // returns a class object that implements the typeclass/protocol - this is an implementation of the typeclass/protocol for a given type
464
+ get(classConstructor, name) {
465
+ const protocolImplGroup = this.implementations.get(classConstructor);
466
+ if (!protocolImplGroup) {
467
+ throw new Error(`Protocol ${this.name} not implemented by ${classConstructor}`);
468
+ }
469
+ const impl = protocolImplGroup.get(name);
470
+ if (!impl) {
471
+ throw new Error(
472
+ `Named implementation '${name || protocolImplGroup.defaultImpl}' of protocol ${this.name} not implemented by ${classConstructor}`
473
+ );
474
+ }
475
+ return impl;
476
+ }
477
+ };
478
+ var Protocol = class _Protocol {
479
+ static protocolRegistryLock;
480
+ static registry;
481
+ static {
482
+ _Protocol.protocolRegistryLock = _Protocol.protocolRegistryLock || new Mutex();
483
+ _Protocol.registry = _Protocol.registry || /* @__PURE__ */ new Map();
484
+ }
485
+ static getTypeclass() {
486
+ if (this.registry.has(this.name)) {
487
+ return this.registry.get(this.name);
488
+ } else {
489
+ const newTypeclass = new ImplementationRegistry(this.name);
490
+ this.registry.set(this.name, newTypeclass);
491
+ return newTypeclass;
492
+ }
493
+ }
494
+ // static getTypeclass(): ImplementationRegistry {
495
+ // if (global.protocolRegistry.has(this.name)) {
496
+ // return global.protocolRegistry.get(this.name) as ImplementationRegistry;
497
+ // } else {
498
+ // const newTypeclass = new ImplementationRegistry(this.name);
499
+ // global.protocolRegistry.set(this.name, newTypeclass);
500
+ // return newTypeclass;
501
+ // }
502
+ // }
503
+ static async register(classConstructor, implClass, makeDefault = false) {
504
+ await _Protocol.protocolRegistryLock.runExclusive(async () => {
505
+ let typeclass = this.getTypeclass();
506
+ typeclass.register(classConstructor, implClass, makeDefault);
507
+ });
508
+ }
509
+ static async use(classConstructor, implClass) {
510
+ await _Protocol.protocolRegistryLock.runExclusive(async () => {
511
+ let typeclass = this.getTypeclass();
512
+ typeclass.use(classConstructor, implClass);
513
+ });
514
+ }
515
+ // returns a class object that implements the typeclass - this is an implementation of the typeclass for a given type
516
+ static get(classConstructor, name) {
517
+ const typeclass = this.getTypeclass();
518
+ return typeclass.get(classConstructor, name);
519
+ }
520
+ // returns an instance of the class that implements the typeclass
521
+ // For example, Enumerable.for([1,2,3]) returns a new instance of the EnumerableArray implementation of the
522
+ // Enumerable typeclass on the Array type, initialized with [1,2,3] in the constructor
523
+ static for(instance, explicitImplClass) {
524
+ let finalImplConstructor;
525
+ if (explicitImplClass) {
526
+ finalImplConstructor = explicitImplClass;
527
+ } else {
528
+ finalImplConstructor = this.get(
529
+ kind(instance)
530
+ // No name needed here for the default lookup via this.get(typeConstructor)
531
+ );
532
+ }
533
+ return new finalImplConstructor(instance);
534
+ }
535
+ };
536
+
537
+ // src/flex/compactable.ts
538
+ var Compactable = class extends Protocol {
539
+ constructor(self) {
540
+ super();
541
+ this.self = self;
542
+ }
543
+ compact(omit) {
544
+ throw new Error("not implemented");
545
+ }
546
+ };
547
+ function compact(omit) {
548
+ return function(compactableVal) {
549
+ return Compactable.for(compactableVal).compact(omit);
550
+ };
551
+ }
552
+
553
+ // src/flex/enumerator.ts
554
+ var Enumerator = class _Enumerator {
555
+ constructor(iterator) {
556
+ this.iterator = iterator;
557
+ }
558
+ static for(iterator) {
559
+ return new _Enumerator(iterator);
560
+ }
561
+ all(predFn) {
562
+ for (const elem of this) {
563
+ if (!predFn(elem)) {
564
+ return false;
565
+ }
566
+ }
567
+ return true;
568
+ }
569
+ any(predFn) {
570
+ for (const elem of this) {
571
+ if (predFn(elem)) {
572
+ return true;
573
+ }
574
+ }
575
+ return false;
576
+ }
577
+ each(visitorFn) {
578
+ for (const e of this) {
579
+ visitorFn(e);
580
+ }
581
+ }
582
+ first(predFn = identity, defaultIfAbsesnt = void 0) {
583
+ for (const e of this) {
584
+ if (predFn(e)) return e;
585
+ }
586
+ return defaultIfAbsesnt;
587
+ }
588
+ map(mapFn) {
589
+ function* gen(iterator) {
590
+ for (const e of iterator) {
591
+ yield mapFn(e);
592
+ }
593
+ }
594
+ return _Enumerator.for(gen(this));
595
+ }
596
+ // per https://typescript.tv/hands-on/all-you-need-to-know-about-iterators-and-generators/#the-iterator-protocol
597
+ // implement the IterableIterator interface by implementing [Symbol.iterator]() and next()
598
+ [Symbol.iterator]() {
599
+ return this;
600
+ }
601
+ // per https://typescript.tv/hands-on/all-you-need-to-know-about-iterators-and-generators/#the-iterator-protocol
602
+ // implement the IterableIterator interface by implementing [Symbol.iterator]() and next()
603
+ next() {
604
+ const val = this.produce();
605
+ if (val instanceof End) {
606
+ return { done: true, value: End.instance };
607
+ }
608
+ return { done: false, value: val };
609
+ }
610
+ // this produces either the next value in the Enumerator, or it returns End
611
+ produce() {
612
+ const val = this.iterator.next();
613
+ if (val.done) {
614
+ return End.instance;
615
+ }
616
+ return val.value;
617
+ }
618
+ select(predFn) {
619
+ function* gen(iterator) {
620
+ for (const e of iterator) {
621
+ if (predFn(e)) yield e;
622
+ }
623
+ }
624
+ return _Enumerator.for(gen(this));
625
+ }
626
+ toArray() {
627
+ return Array.from(this);
628
+ }
629
+ toMap(kvMapFn) {
630
+ return new Map(this.map(kvMapFn));
631
+ }
632
+ toSet() {
633
+ return new Set(this);
634
+ }
635
+ };
636
+
637
+ // src/flex/enumerable.ts
638
+ var Enumerable = class extends Protocol {
639
+ constructor(self) {
640
+ super();
641
+ this.self = self;
642
+ }
643
+ static each = each;
644
+ each(visitorFn) {
645
+ return this.toEnumerator().each(visitorFn);
646
+ }
647
+ *emit() {
648
+ throw new Error("emit not implemented");
649
+ }
650
+ toArray() {
651
+ return this.toEnumerator().toArray();
652
+ }
653
+ toEnumerator() {
654
+ return Enumerator.for(this.emit());
655
+ }
656
+ toSet() {
657
+ return this.toEnumerator().toSet();
658
+ }
659
+ };
660
+ function each(visitorFn) {
661
+ return function(enumerableVal, implClass) {
662
+ return Enumerable.for(enumerableVal, implClass).each(visitorFn);
663
+ };
664
+ }
665
+
666
+ // src/flex/mappable.ts
667
+ var Mappable = class extends Protocol {
668
+ constructor(self) {
669
+ super();
670
+ this.self = self;
671
+ }
672
+ map(mapFn) {
673
+ throw new Error("not implemented");
674
+ }
675
+ };
676
+ function map(mapFn) {
677
+ return function(mappableVal, explicitImplClass) {
678
+ return Mappable.for(mappableVal, explicitImplClass).map(mapFn);
679
+ };
680
+ }
681
+ var AsyncMappable = class extends Protocol {
682
+ constructor(self) {
683
+ super();
684
+ this.self = self;
685
+ }
686
+ async map(mapFn) {
687
+ throw new Error("not implemented");
688
+ }
689
+ };
690
+ function asyncMap(mapFn) {
691
+ return async function(mappableVal, explicitImplClass) {
692
+ return await AsyncMappable.for(mappableVal, explicitImplClass).map(mapFn);
693
+ };
694
+ }
695
+
696
+ // src/flex/selectable.ts
697
+ var Selectable = class extends Protocol {
698
+ constructor(self) {
699
+ super();
700
+ this.self = self;
701
+ }
702
+ select(predFn) {
703
+ throw new Error("not implemented");
704
+ }
705
+ };
706
+ function select(predFn) {
707
+ return function(selectableVal, implClass) {
708
+ return Selectable.for(selectableVal, implClass).select(predFn);
709
+ };
710
+ }
711
+
712
+ // src/flex/array-protocols.ts
713
+ var CompactableArray = class extends Compactable {
714
+ compact(omit) {
715
+ omit ||= [null, void 0];
716
+ const omitSet = toSet(omit);
717
+ const newArr = [];
718
+ for (const val of this.self) {
719
+ if (!omitSet.has(val)) {
720
+ newArr.push(val);
721
+ }
722
+ }
723
+ return newArr;
724
+ }
725
+ };
726
+ await Compactable.register(Array, CompactableArray, true);
727
+ var MappableArray = class extends Mappable {
728
+ map(mapFn) {
729
+ return this.self.map(mapFn);
730
+ }
731
+ };
732
+ await Mappable.register(Array, MappableArray, true);
733
+ var AsyncMappableArray = class extends AsyncMappable {
734
+ async map(mapFn) {
735
+ const arr = [];
736
+ for await (const v of this.self) {
737
+ const mappedValue = await mapFn(v);
738
+ arr.push(mappedValue);
739
+ }
740
+ return arr;
741
+ }
742
+ };
743
+ await AsyncMappable.register(Array, AsyncMappableArray, true);
744
+ var EnumerableArray = class extends Enumerable {
745
+ *emit() {
746
+ for (const e of this.self) yield e;
747
+ }
748
+ };
749
+ await Enumerable.register(Array, EnumerableArray, true);
750
+ var SelectableArray = class extends Selectable {
751
+ select(predFn) {
752
+ return this.self.filter(predFn);
753
+ }
754
+ };
755
+ await Selectable.register(Array, SelectableArray, true);
756
+
757
+ // src/flex/map.ts
758
+ function each2(eachFn) {
759
+ return function(map6) {
760
+ map6.forEach((value, key) => {
761
+ eachFn([key, value]);
762
+ });
763
+ };
764
+ }
765
+ function isEmpty(map6) {
766
+ return map6.size === 0;
767
+ }
768
+ function keys(map6) {
769
+ return Array.from(map6.keys());
770
+ }
771
+ function map2(mapFn) {
772
+ return function(map6) {
773
+ const m = /* @__PURE__ */ new Map();
774
+ map6.forEach((value, key) => {
775
+ const [newKey, newValue] = mapFn([key, value]);
776
+ m.set(newKey, newValue);
777
+ });
778
+ return m;
779
+ };
780
+ }
781
+ function select2(predFn) {
782
+ return function(map6) {
783
+ const newMap = /* @__PURE__ */ new Map();
784
+ map6.forEach((value, key) => {
785
+ if (predFn([key, value])) {
786
+ newMap.set(key, value);
787
+ }
788
+ });
789
+ return newMap;
790
+ };
791
+ }
792
+ function toObject(map6) {
793
+ return Object.fromEntries(map6.entries());
794
+ }
795
+ function values(map6) {
796
+ return Array.from(map6.values());
797
+ }
798
+ var M = buildWrapperProxy();
799
+ M.registerUncurriedFns({ isEmpty, keys, toObject, values });
800
+ M.registerCurriedFns({ each: each2, map: map2, select: select2 });
801
+ M.registerStatic({
802
+ each: each2,
803
+ isEmpty,
804
+ keys,
805
+ map: map2,
806
+ select: select2,
807
+ toObject,
808
+ values
809
+ });
810
+
811
+ // src/flex/map-protocols.ts
812
+ var MapToMap = class extends Mappable {
813
+ map(mapFn) {
814
+ const m = /* @__PURE__ */ new Map();
815
+ this.self.forEach((value, key) => {
816
+ const [newKey, newValue] = mapFn([key, value]);
817
+ m.set(newKey, newValue);
818
+ });
819
+ return m;
820
+ }
821
+ };
822
+ var MapToArray = class extends Mappable {
823
+ map(mapFn) {
824
+ const arr = [];
825
+ this.self.forEach((value, key) => {
826
+ const mappedValue = mapFn([key, value]);
827
+ arr.push(mappedValue);
828
+ });
829
+ return arr;
830
+ }
831
+ };
832
+ await Mappable.register(Map, MapToMap, true);
833
+ await Mappable.register(Map, MapToArray);
834
+ var AsyncMapToMap = class extends AsyncMappable {
835
+ async map(mapFn) {
836
+ const m = /* @__PURE__ */ new Map();
837
+ for await (const [key, value] of this.self) {
838
+ const [newKey, newValue] = await mapFn([key, value]);
839
+ m.set(newKey, newValue);
840
+ }
841
+ return m;
842
+ }
843
+ };
844
+ var AsyncMapToArray = class extends AsyncMappable {
845
+ async map(mapFn) {
846
+ const arr = [];
847
+ for await (const [key, value] of this.self) {
848
+ const mappedValue = await mapFn([key, value]);
849
+ arr.push(mappedValue);
850
+ }
851
+ return arr;
852
+ }
853
+ };
854
+ await AsyncMappable.register(Map, AsyncMapToMap, true);
855
+ await AsyncMappable.register(Map, AsyncMapToArray);
856
+ var EnumerablePair = class extends Enumerable {
857
+ *emit() {
858
+ for (const [k, v] of this.self.entries()) {
859
+ yield [k, v];
860
+ }
861
+ }
862
+ };
863
+ var EnumerableKey = class extends Enumerable {
864
+ *emit() {
865
+ for (const k of this.self.keys()) {
866
+ yield k;
867
+ }
868
+ }
869
+ };
870
+ var EnumerableValue = class extends Enumerable {
871
+ *emit() {
872
+ for (const v of this.self.values()) {
873
+ yield v;
874
+ }
875
+ }
876
+ };
877
+ await Enumerable.register(Map, EnumerablePair, true);
878
+ await Enumerable.register(Map, EnumerableKey);
879
+ await Enumerable.register(Map, EnumerableValue);
880
+ var SelectableMap = class extends Selectable {
881
+ select(predFn) {
882
+ return M(this.self).select(predFn);
883
+ }
884
+ };
885
+ await Selectable.register(Map, SelectableMap, true);
886
+
887
+ // src/flex/object.ts
888
+ function each3(eachFn) {
889
+ return function(object) {
890
+ Object.entries(object).forEach((kvPair) => {
891
+ eachFn(kvPair);
892
+ });
893
+ };
894
+ }
895
+ function keys2(object) {
896
+ return Object.keys(object);
897
+ }
898
+ function map3(mapFn) {
899
+ return function(object) {
900
+ const obj = {};
901
+ Object.entries(object).forEach((kvPair) => {
902
+ const [newKey, newValue] = mapFn(kvPair);
903
+ obj[newKey] = newValue;
904
+ });
905
+ return obj;
906
+ };
907
+ }
908
+ function pick(keysToPick) {
909
+ return select3(([key, _]) => keysToPick.has(key));
910
+ }
911
+ function select3(predFn) {
912
+ return function(object) {
913
+ const obj = {};
914
+ Object.entries(object).forEach((kvPair) => {
915
+ if (predFn(kvPair)) {
916
+ const [k, v] = kvPair;
917
+ obj[k] = v;
918
+ }
919
+ });
920
+ return obj;
921
+ };
922
+ }
923
+ function toMap(object) {
924
+ return new Map(Object.entries(object));
925
+ }
926
+ function values2(object) {
927
+ return Object.values(object);
928
+ }
929
+ var O = buildWrapperProxy();
930
+ O.registerUncurriedFns({ keys: keys2, toMap, values: values2 });
931
+ O.registerCurriedFns({ each: each3, map: map3, pick, select: select3 });
932
+ O.registerStatic({
933
+ each: each3,
934
+ // (eachFn)(obj) => ...
935
+ keys: keys2,
936
+ // (obj) => ...
937
+ map: map3,
938
+ pick,
939
+ select: select3,
940
+ toMap,
941
+ values: values2
942
+ });
943
+
944
+ // src/flex/object-protocols.ts
945
+ var SelectableObject = class extends Selectable {
946
+ select(predFn) {
947
+ return O(this.self).select(predFn);
948
+ }
949
+ };
950
+ await Selectable.register(Object, SelectableObject, true);
951
+
952
+ // src/flex/path.ts
953
+ import { copyFile, cp } from "fs/promises";
954
+ import { existsSync, lstatSync } from "fs";
955
+ import { win32, posix } from "path";
956
+ import { homedir } from "os";
957
+ import { globSync } from "glob";
958
+
959
+ // src/flex/platform.ts
960
+ import { platform as nodePlatform } from "process";
961
+ function isWindows() {
962
+ return platform() == "win32";
963
+ }
964
+ function platform() {
965
+ return nodePlatform;
966
+ }
967
+
968
+ // src/flex/string.ts
969
+ function isEmpty2(str) {
970
+ return str.length == 0;
971
+ }
972
+ function isNumeric(str) {
973
+ if (typeof str !== "string") return false;
974
+ const trimmedStr = str.trim();
975
+ if (trimmedStr === "") return false;
976
+ const num = Number(trimmedStr);
977
+ return !isNaN(num) && isFinite(num) && String(num) === trimmedStr;
978
+ }
979
+ function matches(regexp) {
980
+ const globalRegex = regexp.global ? regexp : new RegExp(regexp.source, regexp.flags + "g");
981
+ return function(str) {
982
+ const matches2 = [...str.matchAll(globalRegex)];
983
+ if (matches2.length == 0) return null;
984
+ return matches2;
985
+ };
986
+ }
987
+ var StringProxy = class {
988
+ constructor(str) {
989
+ this.str = str;
990
+ }
991
+ // Add common string properties/methods for TypeScript
992
+ get length() {
993
+ return this.str.length;
994
+ }
995
+ toUpperCase() {
996
+ return this.str.toUpperCase();
997
+ }
998
+ isEmpty() {
999
+ return isEmpty2(this.str);
1000
+ }
1001
+ isNumeric() {
1002
+ return isNumeric(this.str);
1003
+ }
1004
+ matches(regexp) {
1005
+ return matches(regexp)(this.str);
1006
+ }
1007
+ trimPrefix(prefix) {
1008
+ if (this.str.startsWith(prefix)) {
1009
+ return this.str.slice(prefix.length);
1010
+ }
1011
+ return this.str;
1012
+ }
1013
+ trimSuffix(suffix) {
1014
+ if (this.str.endsWith(suffix)) {
1015
+ return this.str.slice(0, this.str.length - suffix.length);
1016
+ }
1017
+ return this.str;
1018
+ }
1019
+ };
1020
+ var Str = function(str) {
1021
+ const handler = {
1022
+ get(target, property, receiver) {
1023
+ const propOnTarget = Reflect.get(target, property, receiver);
1024
+ if (V.isPresent(propOnTarget)) {
1025
+ return propOnTarget;
1026
+ }
1027
+ const underlyingString = target.str;
1028
+ const valueFromPrimitive = underlyingString[property];
1029
+ if (typeof valueFromPrimitive === "function") {
1030
+ return valueFromPrimitive.bind(underlyingString);
1031
+ }
1032
+ return valueFromPrimitive;
1033
+ }
1034
+ };
1035
+ return new Proxy(new StringProxy(str), handler);
1036
+ };
1037
+ Object.assign(Str, {
1038
+ isEmpty: isEmpty2,
1039
+ isNumeric,
1040
+ matches
1041
+ });
1042
+
1043
+ // src/flex/range.ts
1044
+ var Range = class _Range {
1045
+ constructor(start, end, inclusive = true) {
1046
+ this.start = start;
1047
+ this.end = end;
1048
+ this.inclusive = inclusive;
1049
+ }
1050
+ static new(start, end, inclusive = true) {
1051
+ return new _Range(start, end, inclusive);
1052
+ }
1053
+ each(visitorFn) {
1054
+ const exclusiveEnd = this.inclusive ? this.end + 1 : this.end;
1055
+ for (let i = this.start; i < exclusiveEnd; i++) {
1056
+ visitorFn(i);
1057
+ }
1058
+ }
1059
+ equals(other) {
1060
+ if (!(other instanceof _Range)) {
1061
+ return false;
1062
+ }
1063
+ return this.start === other.start && this.end === other.end && this.inclusive === other.inclusive;
1064
+ }
1065
+ map(mapFn) {
1066
+ const arr = [];
1067
+ this.each((num) => {
1068
+ arr.push(mapFn(num));
1069
+ });
1070
+ return arr;
1071
+ }
1072
+ toArray() {
1073
+ return Enumerable.for(this).toArray();
1074
+ }
1075
+ };
1076
+
1077
+ // src/flex/equal.ts
1078
+ var Equal = class extends Protocol {
1079
+ constructor(self) {
1080
+ super();
1081
+ this.self = self;
1082
+ }
1083
+ equal(val) {
1084
+ throw new Error("equal not implemented");
1085
+ }
1086
+ notEqual(val) {
1087
+ return !this.equal(val);
1088
+ }
1089
+ eq(val) {
1090
+ return this.equal(val);
1091
+ }
1092
+ neq(val) {
1093
+ return this.notEqual(val);
1094
+ }
1095
+ };
1096
+
1097
+ // src/flex/path.ts
1098
+ var Path = class _Path {
1099
+ constructor(path3, isWindowsPath = isWindows()) {
1100
+ this.path = path3;
1101
+ this.isWindowsPath = isWindowsPath;
1102
+ }
1103
+ static new(path3, isWindowsPath = isWindows()) {
1104
+ if (path3 instanceof _Path) {
1105
+ return path3;
1106
+ }
1107
+ return new _Path(path3, isWindowsPath);
1108
+ }
1109
+ static cwd() {
1110
+ return _Path.new(process.cwd());
1111
+ }
1112
+ static homeDir() {
1113
+ return _Path.new(homedir());
1114
+ }
1115
+ static sep(isWindowsPath = isWindows()) {
1116
+ if (isWindowsPath) {
1117
+ return win32.sep;
1118
+ } else {
1119
+ return posix.sep;
1120
+ }
1121
+ }
1122
+ absolute() {
1123
+ return this.resolve();
1124
+ }
1125
+ ancestors() {
1126
+ if (this.isRoot()) {
1127
+ return [];
1128
+ }
1129
+ const ancestors = [];
1130
+ let current = this.directory();
1131
+ while (true) {
1132
+ ancestors.push(current);
1133
+ if (current.isRoot()) {
1134
+ break;
1135
+ }
1136
+ current = current.parent();
1137
+ }
1138
+ return ancestors;
1139
+ }
1140
+ basename(suffix) {
1141
+ if (this.isWindowsPath) {
1142
+ return this.build(win32.basename(this.path, suffix));
1143
+ } else {
1144
+ return this.build(posix.basename(this.path, suffix));
1145
+ }
1146
+ }
1147
+ build(path3) {
1148
+ return new _Path(path3, this.isWindowsPath);
1149
+ }
1150
+ // returns the path to the destination on success; null otherwise
1151
+ async copy(destPath, mode) {
1152
+ destPath = _Path.new(destPath).normalize();
1153
+ try {
1154
+ if (this.isDirectory()) {
1155
+ await cp(this.toString(), destPath.toString(), { mode, recursive: true });
1156
+ } else {
1157
+ await copyFile(this.toString(), destPath.toString(), mode);
1158
+ }
1159
+ return destPath;
1160
+ } catch (e) {
1161
+ return null;
1162
+ }
1163
+ }
1164
+ dirContains(filename) {
1165
+ return this.isDirectory() && this.glob(filename).length > 0;
1166
+ }
1167
+ directory() {
1168
+ return this.absolute().dirname();
1169
+ }
1170
+ // given a path like "bar/bas", this method converts the path to an absolute path (e.g. "/foo/bar/bas"),
1171
+ // and then returns the directory tree as an array of the form ["foo", "bar", "bas"]
1172
+ directorySegments() {
1173
+ return this.absolute().dirname().split();
1174
+ }
1175
+ dirname() {
1176
+ if (this.isWindowsPath) {
1177
+ return this.build(win32.dirname(this.path));
1178
+ } else {
1179
+ return this.build(posix.dirname(this.path));
1180
+ }
1181
+ }
1182
+ equals(other) {
1183
+ if (!(other instanceof _Path)) {
1184
+ return false;
1185
+ }
1186
+ return this.path === other.path && this.isWindowsPath === other.isWindowsPath;
1187
+ }
1188
+ exists() {
1189
+ return existsSync(this.path);
1190
+ }
1191
+ ext() {
1192
+ return this.parse().ext;
1193
+ }
1194
+ glob(pattern) {
1195
+ const cwd = this.absolute().toString();
1196
+ return globSync(pattern, { cwd }).map((path3) => this.build(path3));
1197
+ }
1198
+ isAbsolute() {
1199
+ if (this.isWindowsPath) {
1200
+ return win32.isAbsolute(this.path);
1201
+ } else {
1202
+ return posix.isAbsolute(this.path);
1203
+ }
1204
+ }
1205
+ isDirectory() {
1206
+ return this.exists() && lstatSync(this.path).isDirectory();
1207
+ }
1208
+ isFile() {
1209
+ return this.exists() && lstatSync(this.path).isFile();
1210
+ }
1211
+ isPosix() {
1212
+ return !this.isWindowsPath;
1213
+ }
1214
+ isRoot() {
1215
+ return this.parse().root === this.normalize().toString();
1216
+ }
1217
+ isWindows() {
1218
+ return this.isWindowsPath;
1219
+ }
1220
+ join(...paths) {
1221
+ if (this.isWindowsPath) {
1222
+ return this.build(win32.join(this.path, ...paths));
1223
+ } else {
1224
+ return this.build(posix.join(this.path, ...paths));
1225
+ }
1226
+ }
1227
+ normalize() {
1228
+ if (this.isWindowsPath) {
1229
+ return this.build(win32.normalize(this.path));
1230
+ } else {
1231
+ return this.build(posix.normalize(this.path));
1232
+ }
1233
+ }
1234
+ parent(count = 1) {
1235
+ let path3 = this.absolute();
1236
+ Range.new(1, count).each((i) => {
1237
+ path3 = path3.resolve("..");
1238
+ });
1239
+ return path3;
1240
+ }
1241
+ // returns an object of the form: { root, dir, base, ext, name }
1242
+ //
1243
+ // Posix:
1244
+ // >>> path.parse('/home/user/dir/file.txt')
1245
+ // { root: '/',
1246
+ // dir: '/home/user/dir',
1247
+ // base: 'file.txt',
1248
+ // ext: '.txt',
1249
+ // name: 'file' }
1250
+ // ┌─────────────────────┬────────────┐
1251
+ // │ dir │ base │
1252
+ // ├──────┬ ├──────┬─────┤
1253
+ // │ root │ │ name │ ext │
1254
+ // " / home/user/dir / file .txt "
1255
+ // └──────┴──────────────┴──────┴─────┘
1256
+ // (All spaces in the "" line should be ignored. They are purely for formatting.)
1257
+ //
1258
+ // Windows:
1259
+ // >>> path.parse('C:\\path\\dir\\file.txt');
1260
+ // { root: 'C:\\',
1261
+ // dir: 'C:\\path\\dir',
1262
+ // base: 'file.txt',
1263
+ // ext: '.txt',
1264
+ // name: 'file' }
1265
+ // ┌─────────────────────┬────────────┐
1266
+ // │ dir │ base │
1267
+ // ├──────┬ ├──────┬─────┤
1268
+ // │ root │ │ name │ ext │
1269
+ // " C:\ path\dir \ file .txt "
1270
+ // └──────┴──────────────┴──────┴─────┘
1271
+ // (All spaces in the "" line should be ignored. They are purely for formatting.)
1272
+ parse() {
1273
+ if (this.isWindowsPath) {
1274
+ return win32.parse(this.path);
1275
+ } else {
1276
+ return posix.parse(this.path);
1277
+ }
1278
+ }
1279
+ pop(count = 1) {
1280
+ return this.parent(count);
1281
+ }
1282
+ relative(to) {
1283
+ if (this.isWindowsPath) {
1284
+ return this.build(win32.relative(this.path, to));
1285
+ } else {
1286
+ return this.build(posix.relative(this.path, to));
1287
+ }
1288
+ }
1289
+ resolve(...paths) {
1290
+ if (this.isWindowsPath) {
1291
+ return this.build(win32.resolve(this.path, ...paths));
1292
+ } else {
1293
+ return this.build(posix.resolve(this.path, ...paths));
1294
+ }
1295
+ }
1296
+ root() {
1297
+ const { root } = this.parse();
1298
+ return this.build(root);
1299
+ }
1300
+ selfAndAncestors() {
1301
+ const ancestors = [];
1302
+ let current = this.absolute();
1303
+ while (true) {
1304
+ ancestors.push(current);
1305
+ if (current.isRoot()) {
1306
+ break;
1307
+ }
1308
+ current = current.parent();
1309
+ }
1310
+ return ancestors;
1311
+ }
1312
+ // returns the parts of the path, excluding the root if applicable
1313
+ // /home/user/dir/file.txt -> ["home", "user", "dir", "file.txt"]
1314
+ // C:\home\user\dir\file.txt -> ["home", "user", "dir", "file.txt"]
1315
+ // user/dir/file.txt -> ["user", "dir", "file.txt"]
1316
+ // user\dir\file.txt -> ["user", "dir", "file.txt"]
1317
+ split() {
1318
+ const normalized = this.normalize();
1319
+ if (normalized.isAbsolute()) {
1320
+ const { root } = normalized.parse();
1321
+ const relPath = Str(normalized.toString()).trimPrefix(root);
1322
+ return relPath.split(_Path.sep(this.isWindowsPath));
1323
+ } else {
1324
+ return normalized.toString().split(_Path.sep(this.isWindowsPath));
1325
+ }
1326
+ }
1327
+ toString() {
1328
+ return this.path;
1329
+ }
1330
+ };
1331
+ var PathEqualImpl = class extends Equal {
1332
+ equal(val) {
1333
+ return this.self.equals(val);
1334
+ }
1335
+ };
1336
+ await Equal.register(Path, PathEqualImpl, true);
1337
+
1338
+ // src/flex/range-protocols.ts
1339
+ var MappableRange = class extends Mappable {
1340
+ map(mapFn) {
1341
+ return this.self.map(mapFn);
1342
+ }
1343
+ };
1344
+ await Mappable.register(Range, MappableRange, true);
1345
+ var EnumerableRange = class extends Enumerable {
1346
+ *emit() {
1347
+ const exclusiveEnd = this.self.inclusive ? this.self.end + 1 : this.self.end;
1348
+ for (let i = this.self.start; i < exclusiveEnd; i++) {
1349
+ yield i;
1350
+ }
1351
+ }
1352
+ };
1353
+ await Enumerable.register(Range, EnumerableRange, true);
1354
+ var EqualRange = class extends Equal {
1355
+ equal(other) {
1356
+ return this.self.equals(other);
1357
+ }
1358
+ };
1359
+ await Equal.register(Range, EqualRange, true);
1360
+ var SelectableRange = class extends Selectable {
1361
+ select(predFn) {
1362
+ let arr = [];
1363
+ this.self.each((num) => {
1364
+ if (predFn(num)) {
1365
+ arr.push(num);
1366
+ }
1367
+ });
1368
+ return arr;
1369
+ }
1370
+ };
1371
+ await Selectable.register(Range, SelectableRange, true);
1372
+
1373
+ // src/flex/set-protocols.ts
1374
+ var MappableSet = class extends Mappable {
1375
+ map(mapFn) {
1376
+ const s = /* @__PURE__ */ new Set();
1377
+ this.self.forEach((value) => {
1378
+ const newValue = mapFn(value);
1379
+ s.add(newValue);
1380
+ });
1381
+ return s;
1382
+ }
1383
+ };
1384
+ await Mappable.register(Set, MappableSet, true);
1385
+ var EnumerableSet = class extends Enumerable {
1386
+ // each(visitorFn: (v: T) => any): any {
1387
+ // return this.self.forEach((v) => visitorFn(v));
1388
+ // }
1389
+ *emit() {
1390
+ for (const e of this.self) {
1391
+ yield e;
1392
+ }
1393
+ }
1394
+ };
1395
+ await Enumerable.register(Set, EnumerableSet, true);
1396
+
1397
+ // src/flex/array.ts
1398
+ function compact2(omit = [null, void 0]) {
1399
+ return function(arr) {
1400
+ const omitSet = toSet(omit);
1401
+ const newArr = [];
1402
+ for (const val of arr) {
1403
+ if (!omitSet.has(val)) {
1404
+ newArr.push(val);
1405
+ }
1406
+ }
1407
+ return newArr;
1408
+ };
1409
+ }
1410
+ function concat(tail) {
1411
+ return function(arr) {
1412
+ return arr.concat(tail);
1413
+ };
1414
+ }
1415
+ function each4(eachFn) {
1416
+ return function(arr) {
1417
+ arr.forEach((value) => {
1418
+ eachFn(value);
1419
+ });
1420
+ };
1421
+ }
1422
+ function find(predicateFn) {
1423
+ return function(arr) {
1424
+ for (const val of arr) {
1425
+ if (predicateFn(val)) {
1426
+ return val;
1427
+ }
1428
+ }
1429
+ return void 0;
1430
+ };
1431
+ }
1432
+ function first(n) {
1433
+ return function(arr) {
1434
+ return arr.slice(0, n);
1435
+ };
1436
+ }
1437
+ function head(arr) {
1438
+ return arr[0];
1439
+ }
1440
+ function isEmpty3(arr) {
1441
+ return arr.length === 0;
1442
+ }
1443
+ function join(separator) {
1444
+ return function(arr) {
1445
+ return arr.join(separator);
1446
+ };
1447
+ }
1448
+ function last(n) {
1449
+ return function(arr) {
1450
+ return arr.slice(arr.length - n, arr.length);
1451
+ };
1452
+ }
1453
+ function map4(mapFn) {
1454
+ return function(arr) {
1455
+ return arr.map(mapFn);
1456
+ };
1457
+ }
1458
+ function nth(n) {
1459
+ return function(arr) {
1460
+ return arr[n];
1461
+ };
1462
+ }
1463
+ function partition(predFn) {
1464
+ return function(arr) {
1465
+ const trueArr = [];
1466
+ const falseArr = [];
1467
+ for (const val of arr) {
1468
+ if (predFn(val)) {
1469
+ trueArr.push(val);
1470
+ } else {
1471
+ falseArr.push(val);
1472
+ }
1473
+ }
1474
+ return [trueArr, falseArr];
1475
+ };
1476
+ }
1477
+ function prepend(...values3) {
1478
+ return function(arr) {
1479
+ arr.splice(0, 0, ...values3);
1480
+ return arr;
1481
+ };
1482
+ }
1483
+ function select4(predFn) {
1484
+ return function(arr) {
1485
+ return arr.filter(predFn);
1486
+ };
1487
+ }
1488
+ function skipFirst(n) {
1489
+ return function(arr) {
1490
+ if (n > arr.length) {
1491
+ n = arr.length;
1492
+ }
1493
+ return arr.slice(n, arr.length);
1494
+ };
1495
+ }
1496
+ function skipLast(n) {
1497
+ return function(arr) {
1498
+ if (n > arr.length) {
1499
+ n = arr.length;
1500
+ }
1501
+ return arr.slice(0, arr.length - n);
1502
+ };
1503
+ }
1504
+ function toSet(arr) {
1505
+ return new Set(arr);
1506
+ }
1507
+ function uniq(arr) {
1508
+ return uniqBy(identity)(arr);
1509
+ }
1510
+ function uniqBy(comparisonValueFn) {
1511
+ return function(arr) {
1512
+ const map6 = /* @__PURE__ */ new Map();
1513
+ for (const val of arr) {
1514
+ const key = comparisonValueFn(val);
1515
+ if (!map6.has(key)) {
1516
+ map6.set(key, val);
1517
+ }
1518
+ }
1519
+ return Array.from(map6.values());
1520
+ };
1521
+ }
1522
+ function zip(other) {
1523
+ return function(arr) {
1524
+ var len = Math.min(arr.length, other.length);
1525
+ var result = Array(len);
1526
+ var idx = 0;
1527
+ while (idx < len) {
1528
+ result[idx] = [arr[idx], other[idx]];
1529
+ idx += 1;
1530
+ }
1531
+ return result;
1532
+ };
1533
+ }
1534
+ var A2 = buildWrapperProxy();
1535
+ A2.registerUncurriedFns({ head, isEmpty: isEmpty3, toSet, uniq });
1536
+ A2.registerCurriedFns({
1537
+ compact: compact2,
1538
+ concat,
1539
+ each: each4,
1540
+ filter: select4,
1541
+ find,
1542
+ first,
1543
+ join,
1544
+ last,
1545
+ map: map4,
1546
+ nth,
1547
+ partition,
1548
+ prepend,
1549
+ select: select4,
1550
+ skipFirst,
1551
+ skipLast,
1552
+ uniqBy,
1553
+ zip
1554
+ });
1555
+ A2.registerStatic({
1556
+ compact: compact2,
1557
+ concat,
1558
+ each: each4,
1559
+ filter: select4,
1560
+ find,
1561
+ first,
1562
+ head,
1563
+ isEmpty: isEmpty3,
1564
+ join,
1565
+ last,
1566
+ map: map4,
1567
+ nth,
1568
+ partition,
1569
+ prepend,
1570
+ select: select4,
1571
+ skipFirst,
1572
+ skipLast,
1573
+ toSet,
1574
+ uniq,
1575
+ uniqBy,
1576
+ zip
1577
+ });
1578
+
1579
+ // src/flex/set.ts
1580
+ function addAll(enumerableVal) {
1581
+ const enumerable = Enumerable.for(enumerableVal);
1582
+ return function(set) {
1583
+ enumerable.each((item) => set.add(item));
1584
+ return set;
1585
+ };
1586
+ }
1587
+ function each5(eachFn) {
1588
+ return function(set) {
1589
+ set.forEach((value) => {
1590
+ eachFn(value);
1591
+ });
1592
+ };
1593
+ }
1594
+ function intersection(enumerableVal) {
1595
+ const enumerable = Enumerable.for(enumerableVal);
1596
+ return function(set) {
1597
+ return set.intersection(enumerable.toSet());
1598
+ };
1599
+ }
1600
+ function isEmpty4(set) {
1601
+ return set.size === 0;
1602
+ }
1603
+ function isSubsetOf(enumerableSuperset) {
1604
+ const enumerable = Enumerable.for(enumerableSuperset);
1605
+ return function(set) {
1606
+ return set.isSubsetOf(enumerable.toSet());
1607
+ };
1608
+ }
1609
+ function isSupersetOf(enumerableSubset) {
1610
+ const enumerable = Enumerable.for(enumerableSubset);
1611
+ return function(set) {
1612
+ return set.isSupersetOf(enumerable.toSet());
1613
+ };
1614
+ }
1615
+ function map5(mapFn) {
1616
+ return function(set) {
1617
+ const s = /* @__PURE__ */ new Set();
1618
+ set.forEach((value) => {
1619
+ const newValue = mapFn(value);
1620
+ s.add(newValue);
1621
+ });
1622
+ return s;
1623
+ };
1624
+ }
1625
+ function toArray(set) {
1626
+ return Array.from(set.values());
1627
+ }
1628
+ function union(enumerableVal) {
1629
+ const enumerable = Enumerable.for(enumerableVal);
1630
+ return function(set) {
1631
+ return set.union(enumerable.toSet());
1632
+ };
1633
+ }
1634
+ var S = buildWrapperProxy();
1635
+ S.registerUncurriedFns({ isEmpty: isEmpty4, toArray });
1636
+ S.registerCurriedFns({ addAll, each: each5, intersection, map: map5, union, isSubsetOf, isSupersetOf });
1637
+ S.registerStatic({
1638
+ addAll,
1639
+ each: each5,
1640
+ intersection,
1641
+ isEmpty: isEmpty4,
1642
+ isSubsetOf,
1643
+ isSupersetOf,
1644
+ map: map5,
1645
+ toArray,
1646
+ union
1647
+ });
1648
+
1649
+ // src/flex/pipe.ts
1650
+ var Pipe = class {
1651
+ constructor(value) {
1652
+ this.value = value;
1653
+ }
1654
+ // e.g. convertMethodsToPipeline(A([1,2,3]))
1655
+ // returns a function that proxies method invocations through to the A([1,2,3]) object but assigns their return value
1656
+ // to this.value in the pipeline, and then returns the pipe object, enabling stuff like this:
1657
+ // P([1,2,3]).A.compact().A.first().value
1658
+ _convertMethodsToPipeline(wrappedValue, nameOfMethodBag) {
1659
+ const self = this;
1660
+ return new Proxy(wrappedValue, {
1661
+ get(target, property, receiver) {
1662
+ const fn = Reflect.get(target, property, receiver);
1663
+ if (fn === void 0) {
1664
+ throw Error(`${nameOfMethodBag} does not have a method named ${String(property)}`);
1665
+ }
1666
+ return function(...args) {
1667
+ self.value = fn.apply(receiver, args);
1668
+ return self;
1669
+ };
1670
+ }
1671
+ });
1672
+ }
1673
+ get A() {
1674
+ return A2(this.value);
1675
+ }
1676
+ get a() {
1677
+ return this._convertMethodsToPipeline(A2(this.value), "A");
1678
+ }
1679
+ compact(...args) {
1680
+ this.value = compact(...args)(this.value);
1681
+ return this;
1682
+ }
1683
+ each(visitorFn) {
1684
+ this.value = each(visitorFn)(this.value);
1685
+ return this;
1686
+ }
1687
+ get F() {
1688
+ return F(this.value);
1689
+ }
1690
+ get f() {
1691
+ return this._convertMethodsToPipeline(F(this.value), "F");
1692
+ }
1693
+ // enumerable() {
1694
+ // this.value = Enumerable.for(this.value);
1695
+ // return this;
1696
+ // }
1697
+ // enumerator() {
1698
+ // this.value = Enumerator.for(this.value);
1699
+ // return this;
1700
+ // }
1701
+ log() {
1702
+ console.log(V.inspect(this.value));
1703
+ return this;
1704
+ }
1705
+ get M() {
1706
+ return M(this.value);
1707
+ }
1708
+ get m() {
1709
+ return this._convertMethodsToPipeline(M(this.value), "M");
1710
+ }
1711
+ map(mapFn, implClass) {
1712
+ this.value = map(mapFn)(this.value, implClass);
1713
+ return this;
1714
+ }
1715
+ asyncMap(mapFn, implClass) {
1716
+ this.value = asyncMap(mapFn)(this.value, implClass);
1717
+ return this;
1718
+ }
1719
+ get O() {
1720
+ return O(this.value);
1721
+ }
1722
+ get o() {
1723
+ return this._convertMethodsToPipeline(O(this.value), "O");
1724
+ }
1725
+ pipe(fn) {
1726
+ this.value = fn(this.value);
1727
+ return this;
1728
+ }
1729
+ get S() {
1730
+ return S(this.value);
1731
+ }
1732
+ get s() {
1733
+ return this._convertMethodsToPipeline(S(this.value), "S");
1734
+ }
1735
+ get Str() {
1736
+ return Str(this.value);
1737
+ }
1738
+ get str() {
1739
+ return this._convertMethodsToPipeline(Str(this.value), "Str");
1740
+ }
1741
+ select(predFn, implClass) {
1742
+ this.value = select(predFn)(this.value, implClass);
1743
+ return this;
1744
+ }
1745
+ toArray() {
1746
+ this.value = Enumerable.for(this.value).toArray();
1747
+ return this;
1748
+ }
1749
+ waitAll() {
1750
+ this.value = Promise.allSettled(this.value).then((results) => {
1751
+ return results.map((result) => {
1752
+ if (result.status === "fulfilled") {
1753
+ return result.value;
1754
+ } else {
1755
+ return new Error(result.reason);
1756
+ }
1757
+ });
1758
+ });
1759
+ return this;
1760
+ }
1761
+ };
1762
+ var VP = function(initial) {
1763
+ return new Pipe(initial);
1764
+ };
1765
+
1766
+ // src/tempfile.ts
1767
+ import "os";
1768
+ import fs from "fs";
1769
+ import os from "os";
1770
+ import path from "path";
1771
+ import process2 from "process";
1772
+
1773
+ // src/flex/file.ts
1774
+ import { readFile as readFile2 } from "fs/promises";
1775
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1776
+ import { win32 as win322, posix as posix2 } from "path";
1777
+ function exists(path3) {
1778
+ return existsSync2(path3);
1779
+ }
1780
+ var File = class {
1781
+ static absolutePath(...paths) {
1782
+ if (isWindows()) {
1783
+ return win322.resolve(...paths);
1784
+ } else {
1785
+ return posix2.resolve(...paths);
1786
+ }
1787
+ }
1788
+ // basename("c:\\foo\\bar\\baz.txt") => "baz.txt"
1789
+ // basename("/tmp/myfile.html") => "myfile.html"
1790
+ static basename(path3, suffix) {
1791
+ if (isWindows()) {
1792
+ return win322.basename(path3, suffix);
1793
+ } else {
1794
+ return posix2.basename(path3, suffix);
1795
+ }
1796
+ }
1797
+ static exists(path3) {
1798
+ return exists(path3);
1799
+ }
1800
+ static join(...paths) {
1801
+ if (isWindows()) {
1802
+ return win322.join(...paths);
1803
+ } else {
1804
+ return posix2.join(...paths);
1805
+ }
1806
+ }
1807
+ static readSync(path3) {
1808
+ return readFileSync2(path3, {
1809
+ encoding: "utf8"
1810
+ });
1811
+ }
1812
+ static async readAsync(path3) {
1813
+ return await readFile2(path3, {
1814
+ encoding: "utf8"
1815
+ });
1816
+ }
1817
+ };
1818
+
1819
+ // src/tempfile.ts
1820
+ var TmpFileRegistry = class _TmpFileRegistry {
1821
+ static _instance;
1822
+ static get instance() {
1823
+ const path3 = File.join(osHomeDir(), ".hostctl", "tmpstatic");
1824
+ this._instance ??= new _TmpFileRegistry(path3);
1825
+ return this._instance;
1826
+ }
1827
+ rootPath;
1828
+ tempFilePaths;
1829
+ constructor(rootPath) {
1830
+ this.rootPath = Path.new(rootPath);
1831
+ this.tempFilePaths = [];
1832
+ process2.on("exit", (code) => this.exitCallback());
1833
+ }
1834
+ randName() {
1835
+ return Math.random().toString(36).slice(-5) + Math.random().toString(36).slice(-5);
1836
+ }
1837
+ tmpPath(tmpName) {
1838
+ tmpName ??= this.randName();
1839
+ return this.rootPath.join(tmpName);
1840
+ }
1841
+ // this directory will be automatically cleaned up at program exit
1842
+ createNamedTmpDir(subDirName) {
1843
+ const path3 = this.tmpPath(subDirName);
1844
+ fs.mkdirSync(path3.toString(), { recursive: true });
1845
+ this.registerTempFileOrDir(path3.toString());
1846
+ return path3;
1847
+ }
1848
+ // this file will be automatically cleaned up at program exit
1849
+ writeTmpFile(fileContent) {
1850
+ const path3 = this.tmpPath();
1851
+ fs.writeFileSync(path3.toString(), fileContent);
1852
+ this.registerTempFileOrDir(path3.toString());
1853
+ return path3;
1854
+ }
1855
+ exitCallback() {
1856
+ this.cleanupTempFiles();
1857
+ }
1858
+ registerTempFileOrDir(path3) {
1859
+ this.tempFilePaths.push(path3);
1860
+ }
1861
+ cleanupTempFiles() {
1862
+ this.tempFilePaths.forEach((path3) => {
1863
+ this.rmFile(path3);
1864
+ });
1865
+ this.tempFilePaths = [];
1866
+ }
1867
+ rmFile(path3) {
1868
+ try {
1869
+ fs.rmSync(path3, { force: true, recursive: true });
1870
+ } catch (e) {
1871
+ }
1872
+ }
1873
+ };
1874
+ function osHomeDir() {
1875
+ return path.resolve(os.homedir());
1876
+ }
1877
+ function writeTmpFile(fileContents) {
1878
+ return TmpFileRegistry.instance.writeTmpFile(fileContents);
1879
+ }
1880
+
1881
+ // src/host.ts
1882
+ var Host = class {
1883
+ constructor(config, opts) {
1884
+ this.config = config;
1885
+ this.hostname = opts.hostname;
1886
+ this.alias = opts.alias;
1887
+ this.port = opts.port || 22;
1888
+ this.user = opts.user;
1889
+ this.password = opts.password;
1890
+ this.sshKey = opts.sshKey;
1891
+ this.tags = opts.tags ?? [];
1892
+ this.tagSet = new Set(this.tags);
1893
+ }
1894
+ hostname;
1895
+ // The network hostname or IP address, from the YAML hosts map key
1896
+ alias;
1897
+ port;
1898
+ user;
1899
+ password;
1900
+ sshKey;
1901
+ tags;
1902
+ tagSet;
1903
+ async decryptedPassword() {
1904
+ switch (kind(this.password)) {
1905
+ case String:
1906
+ return this.password;
1907
+ case SecretRef:
1908
+ const secretRef = this.password;
1909
+ const secret = this.config.getSecret(secretRef.name);
1910
+ return await secret?.plaintext();
1911
+ }
1912
+ }
1913
+ async decryptedSshKey() {
1914
+ if (V(this.sshKey).isA(SecretRef)) {
1915
+ const secretRef = this.sshKey;
1916
+ const secret = this.config.getSecret(secretRef.name);
1917
+ return await secret?.plaintext();
1918
+ }
1919
+ }
1920
+ toYAML() {
1921
+ let passwordForYaml = this.password;
1922
+ if (this.password && V(this.password).isA(SecretRef)) {
1923
+ passwordForYaml = this.password.toYAML();
1924
+ }
1925
+ let sshKeyForYaml = this.sshKey;
1926
+ if (this.sshKey && V(this.sshKey).isA(SecretRef)) {
1927
+ sshKeyForYaml = this.sshKey.toYAML();
1928
+ }
1929
+ return {
1930
+ alias: this.alias,
1931
+ user: this.user,
1932
+ port: this.port === 22 ? void 0 : this.port,
1933
+ // Only include port if not default
1934
+ password: passwordForYaml,
1935
+ "ssh-key": sshKeyForYaml,
1936
+ tags: this.tags
1937
+ };
1938
+ }
1939
+ // domain logic
1940
+ get shortName() {
1941
+ return this.alias || this.hostname;
1942
+ }
1943
+ isLocal() {
1944
+ return this.hostname === "localhost" || this.hostname === "127.0.0.1";
1945
+ }
1946
+ get uri() {
1947
+ return this.hostname;
1948
+ }
1949
+ // returns the temporary path to the decrypted ssh key
1950
+ async writeTmpDecryptedSshKey() {
1951
+ const content = await this.decryptedSshKey();
1952
+ if (content) {
1953
+ return writeTmpFile(content).toString();
1954
+ }
1955
+ }
1956
+ _plaintextSshKeyPath = void 0;
1957
+ async plaintextSshKeyPath() {
1958
+ if (this._plaintextSshKeyPath) {
1959
+ return this._plaintextSshKeyPath;
1960
+ }
1961
+ switch (kind(this.sshKey)) {
1962
+ case String:
1963
+ this._plaintextSshKeyPath = this.sshKey;
1964
+ break;
1965
+ case SecretRef:
1966
+ this._plaintextSshKeyPath = await this.writeTmpDecryptedSshKey();
1967
+ break;
1968
+ }
1969
+ return this._plaintextSshKeyPath;
1970
+ }
1971
+ toObject() {
1972
+ return {
1973
+ hostname: this.hostname,
1974
+ port: this.port,
1975
+ alias: this.alias,
1976
+ user: this.user,
1977
+ tags: this.tags
1978
+ };
1979
+ }
1980
+ hasTag(tag) {
1981
+ return this.tagSet.has(tag);
1982
+ }
1983
+ // each tag is a string of the form:
1984
+ // - foo
1985
+ // - bar,baz
1986
+ // the interpretation of a 'bar,baz' tag is that the host must have either the 'bar' tag or the 'baz' tag in order to consider the 'bar,baz' tag present
1987
+ hasAllTags(tags) {
1988
+ for (const tag of tags) {
1989
+ const tagParts = tag.split(",");
1990
+ const anyTagPartPresent = S(this.tagSet).intersection(tagParts).size > 0;
1991
+ if (!anyTagPartPresent) return false;
1992
+ }
1993
+ return true;
1994
+ }
1995
+ // each tag is a string of the form:
1996
+ // - foo
1997
+ // - bar+baz
1998
+ // the interpretation of a 'bar+baz' tag is that the host must have both the 'bar' tag and the 'baz' tag in order to consider the 'bar+baz' tag present
1999
+ hasAnyTag(tags) {
2000
+ const effectiveTagsForMatching = S(new Set(this.tagSet)).addAll(A2([this.hostname, this.alias]).compact());
2001
+ for (const tag of tags) {
2002
+ const tagParts = tag.split("+");
2003
+ const allTagPartsPresent = S(effectiveTagsForMatching).isSupersetOf(tagParts);
2004
+ if (allTagPartsPresent) return true;
2005
+ }
2006
+ return false;
2007
+ }
2008
+ };
2009
+
2010
+ // src/age-encryption.ts
2011
+ import fs2 from "fs";
2012
+ import * as age from "age-encryption";
2013
+
2014
+ // src/process.ts
2015
+ import spawnAsync from "@expo/spawn-async";
2016
+
2017
+ // src/age-encryption.ts
2018
+ function readIdentityStringFromFile(path3) {
2019
+ const contents = fs2.readFileSync(path3, {
2020
+ encoding: "utf8"
2021
+ });
2022
+ const identityString = contents.split(/\r?\n|\r|\n/g).map((line) => line.trim()).find((line) => line.startsWith("AGE-SECRET-KEY-1"));
2023
+ if (!identityString) throw new Error(`Unable to read identity from file: ${path3}`);
2024
+ return identityString;
2025
+ }
2026
+ var LibraryDriver = class {
2027
+ async encrypt(valueToEncrypt, publicKeys) {
2028
+ const e = new age.Encrypter();
2029
+ publicKeys.forEach((key) => e.addRecipient(key));
2030
+ const ciphertextBytes = await e.encrypt(valueToEncrypt);
2031
+ return age.armor.encode(ciphertextBytes);
2032
+ }
2033
+ async decrypt(valueToDecryptArmored, privateKeyFilePaths) {
2034
+ if (privateKeyFilePaths.length === 0) {
2035
+ throw new Error("Unable to decrypt: No age encryption identity (private key) specified.");
2036
+ }
2037
+ const d = new age.Decrypter();
2038
+ let identitiesAdded = 0;
2039
+ for (const path3 of privateKeyFilePaths) {
2040
+ try {
2041
+ const identityString = readIdentityStringFromFile(path3);
2042
+ d.addIdentity(identityString);
2043
+ identitiesAdded++;
2044
+ } catch (err) {
2045
+ console.warn(`Failed to read or parse identity file ${path3}, skipping: ${err.message}`);
2046
+ }
2047
+ }
2048
+ if (identitiesAdded === 0) {
2049
+ return null;
2050
+ }
2051
+ try {
2052
+ const ciphertextBytes = age.armor.decode(valueToDecryptArmored);
2053
+ const decryptedText = await d.decrypt(ciphertextBytes, "text");
2054
+ return decryptedText;
2055
+ } catch (error) {
2056
+ return null;
2057
+ }
2058
+ }
2059
+ };
2060
+ var Identity = class {
2061
+ identityFilePath;
2062
+ identity;
2063
+ // either the path to an identity file or an identity string must be supplied
2064
+ constructor({ path: path3, identity: identity2 }) {
2065
+ if (identity2) {
2066
+ this.identity = identity2;
2067
+ this.identityFilePath = this.writeTmpIdentityFile(identity2);
2068
+ } else if (path3) {
2069
+ this.identity = this.readIdentityFromFile(path3);
2070
+ this.identityFilePath = path3;
2071
+ } else {
2072
+ throw "Either an identity string or an identity file path must be supplied to create an Age Encryption identity";
2073
+ }
2074
+ }
2075
+ get recipient() {
2076
+ return age.identityToRecipient(this.identity);
2077
+ }
2078
+ // returns the path to the temp file
2079
+ writeTmpIdentityFile(identity2) {
2080
+ return writeTmpFile(identity2).toString();
2081
+ }
2082
+ readIdentityFromFile(path3) {
2083
+ const contents = fs2.readFileSync(path3, {
2084
+ encoding: "utf8"
2085
+ });
2086
+ const identityString = contents.split(/\r?\n|\r|\n/g).map((line) => line.trim()).find((line) => line.startsWith("AGE-SECRET-KEY-1"));
2087
+ if (!identityString) throw new Error(`Unable to read identity file: ${path3}`);
2088
+ return identityString;
2089
+ }
2090
+ get privateKey() {
2091
+ return this.identity;
2092
+ }
2093
+ get publicKey() {
2094
+ return this.recipient;
2095
+ }
2096
+ decrypt(encryptedString) {
2097
+ return AgeEncryption.decrypt(encryptedString, [this.identityFilePath]);
2098
+ }
2099
+ async encrypt(plaintext) {
2100
+ return AgeEncryption.encrypt(plaintext, [await this.recipient]);
2101
+ }
2102
+ };
2103
+ var AgeEncryption = class {
2104
+ static instance = new LibraryDriver();
2105
+ // Use LibraryDriver by default
2106
+ static async encrypt(valueToEncrypt, publicKeys) {
2107
+ const encrypted = await this.instance.encrypt(valueToEncrypt, publicKeys);
2108
+ return encrypted;
2109
+ }
2110
+ static async decrypt(valueToDecrypt, privateKeyFilePaths) {
2111
+ return await this.instance.decrypt(valueToDecrypt, privateKeyFilePaths);
2112
+ }
2113
+ };
2114
+
2115
+ // src/config-file.ts
2116
+ var SecretRef = class {
2117
+ constructor(name) {
2118
+ this.name = name;
2119
+ }
2120
+ toYAML() {
2121
+ return this;
2122
+ }
2123
+ };
2124
+ var SecretValue2 = class {
2125
+ constructor(value) {
2126
+ this.value = value;
2127
+ }
2128
+ async encrypt(recipients) {
2129
+ if (!this.isEncrypted()) {
2130
+ this.value = await AgeEncryption.encrypt(this.value, recipients);
2131
+ }
2132
+ return this.value;
2133
+ }
2134
+ async decrypt(identities) {
2135
+ if (this.isEncrypted()) {
2136
+ const decryptedValue = await AgeEncryption.decrypt(
2137
+ this.value,
2138
+ identities.map((i) => i.identityFilePath)
2139
+ );
2140
+ if (!decryptedValue) {
2141
+ return void 0;
2142
+ }
2143
+ this.value = decryptedValue;
2144
+ }
2145
+ return this.value;
2146
+ }
2147
+ isEncrypted() {
2148
+ return this.value.includes("AGE ENCRYPTED FILE");
2149
+ }
2150
+ toYAML() {
2151
+ return this.value;
2152
+ }
2153
+ };
2154
+ var Secret2 = class {
2155
+ constructor(config, name, ids, value) {
2156
+ this.config = config;
2157
+ this.name = name;
2158
+ this.ids = ids;
2159
+ this.value = value;
2160
+ }
2161
+ recipients() {
2162
+ return this.ids.recipients();
2163
+ }
2164
+ async ciphertext() {
2165
+ return await this.value.encrypt(this.recipients());
2166
+ }
2167
+ async plaintext() {
2168
+ const identities = this.config.loadPrivateKeys();
2169
+ return await this.value.decrypt(identities);
2170
+ }
2171
+ async encrypt() {
2172
+ return await this.ciphertext();
2173
+ }
2174
+ async decrypt() {
2175
+ return await this.plaintext();
2176
+ }
2177
+ toYAML() {
2178
+ return {
2179
+ ids: this.ids.toYAML(),
2180
+ value: this.value.toYAML()
2181
+ };
2182
+ }
2183
+ };
2184
+ var RecipientGroup = class {
2185
+ constructor(config, publicKeys, idRefs) {
2186
+ this.config = config;
2187
+ this.publicKeys = publicKeys;
2188
+ this.idRefs = idRefs;
2189
+ }
2190
+ // returns an array of public keys associated with all immediate and transitive recipients
2191
+ recipients() {
2192
+ const accumulatedPublicKeys = A2(this.publicKeys).toSet();
2193
+ this.walkRecipientGroups((rg) => {
2194
+ S(accumulatedPublicKeys).addAll(rg.publicKeys);
2195
+ });
2196
+ return S(accumulatedPublicKeys).toArray();
2197
+ }
2198
+ walkRecipientGroups(visitFn, visited = /* @__PURE__ */ new Set()) {
2199
+ if (visited.has(this)) return;
2200
+ visited.add(this);
2201
+ visitFn(this);
2202
+ this.config.getRecipientGroups(this.idRefs).forEach((rg) => rg.walkRecipientGroups(visitFn, visited));
2203
+ }
2204
+ toYAML() {
2205
+ return this.publicKeys.concat(this.idRefs);
2206
+ }
2207
+ };
2208
+ var NamedRecipientGroup = class extends RecipientGroup {
2209
+ constructor(config, name, publicKeys, idRefs) {
2210
+ super(config, publicKeys, idRefs);
2211
+ this.config = config;
2212
+ this.name = name;
2213
+ this.publicKeys = publicKeys;
2214
+ this.idRefs = idRefs;
2215
+ }
2216
+ };
2217
+ var SecretRefYamlType = new yaml.Type("!secret", {
2218
+ kind: "scalar",
2219
+ // 'scalar' means string
2220
+ // Loader must check if the input object is suitable for this type.
2221
+ resolve(data) {
2222
+ return !!data && typeof data === "string" && data.trim().length > 0;
2223
+ },
2224
+ // If a node is resolved, use it to create a SecretRef instance.
2225
+ construct(yamlObj) {
2226
+ return new SecretRef(yamlObj);
2227
+ },
2228
+ // Dumper must process instances of Point by rules of this YAML type.
2229
+ instanceOf: SecretRef,
2230
+ // Dumper must represent SecretRef objects as string in YAML.
2231
+ represent(secretRef) {
2232
+ return secretRef.name;
2233
+ }
2234
+ });
2235
+ var HOSTCTL_CONFIG_SCHEMA = yaml.DEFAULT_SCHEMA.extend([SecretRefYamlType]);
2236
+ var ConfigFile2 = class {
2237
+ constructor(path3) {
2238
+ this.path = path3;
2239
+ this._hosts = /* @__PURE__ */ new Map();
2240
+ this._ids = /* @__PURE__ */ new Map();
2241
+ this._secrets = /* @__PURE__ */ new Map();
2242
+ }
2243
+ _hosts;
2244
+ _ids;
2245
+ _secrets;
2246
+ // serialization and deserialization
2247
+ async load() {
2248
+ if (!File.exists(this.path)) {
2249
+ throw new Error(`Config file ${this.path} doesn't exist. Unable to load.`);
2250
+ }
2251
+ const yamlFileContents = await fsa.readFile(this.path, {
2252
+ encoding: "utf8"
2253
+ });
2254
+ const yamlDocument = yaml.load(yamlFileContents, {
2255
+ schema: HOSTCTL_CONFIG_SCHEMA
2256
+ });
2257
+ this.parse(yamlDocument);
2258
+ }
2259
+ parse(yamlDocument) {
2260
+ this._hosts = this.parseHosts(yamlDocument.hosts || {});
2261
+ this._ids = this.parseIdsMap(yamlDocument.ids || {});
2262
+ this._secrets = this.parseSecrets(yamlDocument.secrets || {});
2263
+ }
2264
+ parseSecretValue(yamlSecretValue) {
2265
+ switch (V(yamlSecretValue).kind()) {
2266
+ case Undefined:
2267
+ return yamlSecretValue;
2268
+ case SecretRef:
2269
+ return yamlSecretValue;
2270
+ case String:
2271
+ return yamlSecretValue;
2272
+ // return new SecretValue(yamlSecretValue as string);
2273
+ default:
2274
+ throw new Error(`unknown secret type: ${V.inspect(yamlSecretValue)}`);
2275
+ }
2276
+ }
2277
+ // yamlHosts is an object
2278
+ parseHosts(yamlHosts) {
2279
+ const hosts = Object.entries(yamlHosts).reduce((hostMap, [hostUri, hostObj]) => {
2280
+ hostObj ||= {};
2281
+ const password = this.parseSecretValue(hostObj.password);
2282
+ const sshKey = this.parseSecretValue(hostObj["ssh-key"]);
2283
+ hostMap.set(
2284
+ hostUri,
2285
+ new Host(this, {
2286
+ ...hostObj,
2287
+ // Use 'as any' to allow spread of hostObj properties
2288
+ hostname: hostUri,
2289
+ // The YAML map key is always the hostname
2290
+ password,
2291
+ sshKey
2292
+ })
2293
+ );
2294
+ return hostMap;
2295
+ }, /* @__PURE__ */ new Map());
2296
+ return hosts;
2297
+ }
2298
+ parseIdsMap(yamlIds) {
2299
+ const ids = Object.entries(yamlIds).reduce((secretsMap, [name, value]) => {
2300
+ value ||= [];
2301
+ const rg = this.parseRecipients(value);
2302
+ secretsMap.set(name, new NamedRecipientGroup(this, name, rg.publicKeys, rg.idRefs));
2303
+ return secretsMap;
2304
+ }, /* @__PURE__ */ new Map());
2305
+ return ids;
2306
+ }
2307
+ parseRecipients(ids) {
2308
+ let idStrings;
2309
+ if (V(ids).isA(String)) {
2310
+ idStrings = [ids];
2311
+ } else if (Array.isArray(ids) && ids.every((item) => V(item).isA(String))) {
2312
+ idStrings = ids;
2313
+ } else if (Array.isArray(ids)) {
2314
+ idStrings = ids.flat(1);
2315
+ } else {
2316
+ idStrings = [];
2317
+ }
2318
+ const publicKeysAndIdRefs = idStrings.flatMap((str) => str.split(",")).map((str) => str.trim());
2319
+ const [publicKeys, idRefs] = A2(publicKeysAndIdRefs).partition(this.isValidPublicKey);
2320
+ return new RecipientGroup(this, publicKeys, idRefs);
2321
+ }
2322
+ // returns true if the key is an Age encryption public key (i.e. a bech32 key with a prefix of 'age')
2323
+ isValidPublicKey(str) {
2324
+ try {
2325
+ const { prefix, words } = bech32.decode(str);
2326
+ return prefix === "age";
2327
+ } catch (e) {
2328
+ return false;
2329
+ }
2330
+ }
2331
+ // yamlSecrets is an object
2332
+ parseSecrets(yamlSecrets) {
2333
+ const secrets = Object.entries(yamlSecrets).reduce((secretsMap, [name, yamlSecret]) => {
2334
+ const recipients = this.parseRecipients(yamlSecret.ids);
2335
+ secretsMap.set(name, new Secret2(this, name, recipients, new SecretValue2(yamlSecret.value)));
2336
+ return secretsMap;
2337
+ }, /* @__PURE__ */ new Map());
2338
+ return secrets;
2339
+ }
2340
+ async save(destinationPath = this.path) {
2341
+ const hosts = this.serializeHosts();
2342
+ const secrets = this.serializeSecrets();
2343
+ const ids = this.serializeIds();
2344
+ const yamlFileObj = {
2345
+ hosts,
2346
+ secrets,
2347
+ ids
2348
+ };
2349
+ const yamlFileContents = yaml.dump(yamlFileObj, {
2350
+ schema: HOSTCTL_CONFIG_SCHEMA,
2351
+ noRefs: true
2352
+ });
2353
+ await fsa.writeFile(destinationPath, yamlFileContents);
2354
+ }
2355
+ serializeHosts() {
2356
+ return VP(this._hosts).map(([name, host]) => [name, host.toYAML()]).m.toObject().value;
2357
+ }
2358
+ serializeSecrets() {
2359
+ return VP(this._secrets).map(([name, secret]) => [name, secret.toYAML()]).m.toObject().value;
2360
+ }
2361
+ serializeIds() {
2362
+ return VP(this._ids).map(([name, id]) => [name, id.toYAML()]).m.toObject().value;
2363
+ }
2364
+ // domain logic
2365
+ // loads private keys from the AGE_IDS environment variable
2366
+ loadPrivateKeys() {
2367
+ const ageIds = process.env.AGE_IDS;
2368
+ if (ageIds) {
2369
+ const paths = globSync2(ageIds);
2370
+ const ids = paths.map((path3) => new Identity({ path: path3 }));
2371
+ return ids;
2372
+ }
2373
+ return [];
2374
+ }
2375
+ getSecret(name) {
2376
+ return this._secrets.get(name);
2377
+ }
2378
+ getRecipientGroups(idRefs) {
2379
+ return VP(idRefs).map((idRef) => this._ids.get(idRef)).compact().value;
2380
+ }
2381
+ async decryptAllIfPossible() {
2382
+ await VP(this._secrets).m.values().map((secret) => secret.decrypt()).waitAll().value;
2383
+ }
2384
+ async encryptAll() {
2385
+ await VP(this._secrets).m.values().map((secret) => secret.encrypt()).waitAll().value;
2386
+ }
2387
+ // Config interface
2388
+ hosts() {
2389
+ return M(this._hosts).values();
2390
+ }
2391
+ secrets() {
2392
+ return this._secrets;
2393
+ }
2394
+ ids() {
2395
+ return this._ids;
2396
+ }
2397
+ };
2398
+
2399
+ // src/config-url.ts
2400
+ var ConfigUrl = class {
2401
+ constructor(url) {
2402
+ this.url = url;
2403
+ this._hosts = /* @__PURE__ */ new Map();
2404
+ this._secrets = /* @__PURE__ */ new Map();
2405
+ this._ids = /* @__PURE__ */ new Map();
2406
+ }
2407
+ _hosts;
2408
+ _secrets;
2409
+ _ids;
2410
+ // Config interface
2411
+ hosts() {
2412
+ return [];
2413
+ }
2414
+ getSecret(name) {
2415
+ return void 0;
2416
+ }
2417
+ getRecipientGroups(idRefs) {
2418
+ return [];
2419
+ }
2420
+ secrets() {
2421
+ return this._secrets;
2422
+ }
2423
+ ids() {
2424
+ return this._ids;
2425
+ }
2426
+ };
2427
+
2428
+ // src/config.ts
2429
+ async function load(configRef) {
2430
+ if (configRef && fs4.existsSync(configRef)) {
2431
+ const configFile = new ConfigFile2(configRef);
2432
+ await configFile.load();
2433
+ return configFile;
2434
+ } else if (configRef) {
2435
+ return new ConfigUrl(configRef);
2436
+ } else {
2437
+ throw new Error("No config specified.");
2438
+ }
2439
+ }
2440
+
2441
+ // src/interaction-handler.ts
2442
+ import { Readable } from "stream";
2443
+ import { StreamRegex } from "stream-regex";
2444
+ var InteractionHandler = class _InteractionHandler {
2445
+ static with(inputs) {
2446
+ return new _InteractionHandler(new Map(Object.entries(inputs)));
2447
+ }
2448
+ patternInputMappings;
2449
+ // pairs of pattern -> response
2450
+ inputStreamForRegexMatcher;
2451
+ // private streamRegex: StreamRegex;
2452
+ // private matches: Readable;
2453
+ writeFn;
2454
+ constructor(patternInputMappings = /* @__PURE__ */ new Map()) {
2455
+ this.patternInputMappings = M(patternInputMappings).map(([pattern, v]) => [
2456
+ pattern instanceof RegExp ? pattern.source : pattern,
2457
+ v
2458
+ ]);
2459
+ }
2460
+ clone() {
2461
+ return new _InteractionHandler(this.patternInputMappings);
2462
+ }
2463
+ with(inputs) {
2464
+ const clone = this.clone();
2465
+ clone.mapInput(inputs);
2466
+ return clone;
2467
+ }
2468
+ mapInput(inputMap) {
2469
+ Object.entries(inputMap).forEach(([pattern, response]) => {
2470
+ this.map(pattern, response);
2471
+ });
2472
+ return this;
2473
+ }
2474
+ map(pattern, response) {
2475
+ if (pattern instanceof RegExp) {
2476
+ this.patternInputMappings.set(pattern.source, response);
2477
+ } else {
2478
+ this.patternInputMappings.set(pattern, response);
2479
+ }
2480
+ return this;
2481
+ }
2482
+ // builds a regex of the form /(alternative 1)|(alternative 2)|...|(alternative N)/
2483
+ compositeRegex() {
2484
+ if (M(this.patternInputMappings).isEmpty()) return void 0;
2485
+ const compositePattern = VP(this.patternInputMappings).m.keys().map((pattern) => `(${pattern})`).a.join("|").value;
2486
+ return new RegExp(compositePattern, "g");
2487
+ }
2488
+ // returns the first pattern that matches
2489
+ // the matching pattern will be a key in ioMappings
2490
+ firstMatchingPattern(matchStr) {
2491
+ return Array.from(this.patternInputMappings.keys()).find((pattern) => matchStr.match(pattern));
2492
+ }
2493
+ findReplacementForMatch(matchStr) {
2494
+ const matchingPattern = this.firstMatchingPattern(matchStr);
2495
+ if (!matchingPattern) return void 0;
2496
+ const replacementValue = this.patternInputMappings.get(matchingPattern);
2497
+ return replacementValue;
2498
+ }
2499
+ begin() {
2500
+ this.inputStreamForRegexMatcher = new Readable();
2501
+ this.inputStreamForRegexMatcher._read = () => {
2502
+ };
2503
+ const compositeRegex = this.compositeRegex();
2504
+ if (compositeRegex) {
2505
+ const streamRegex = new StreamRegex(compositeRegex);
2506
+ const matches2 = streamRegex.match(this.inputStreamForRegexMatcher);
2507
+ matches2.on("data", (matchStr) => {
2508
+ const inputToWrite = this.findReplacementForMatch(matchStr);
2509
+ if (inputToWrite !== void 0) {
2510
+ this.emitInput(inputToWrite);
2511
+ }
2512
+ });
2513
+ }
2514
+ }
2515
+ handleInput(inputChunk) {
2516
+ if (this.inputStreamForRegexMatcher) {
2517
+ this.inputStreamForRegexMatcher.push(inputChunk);
2518
+ }
2519
+ }
2520
+ // either use setOutputToNewReadable XOR setOutputWriter; not both
2521
+ setOutputToNewReadable() {
2522
+ const stdinReadable = new Readable();
2523
+ this.setOutputWriter((str) => stdinReadable.push(str));
2524
+ stdinReadable._read = () => {
2525
+ };
2526
+ return stdinReadable;
2527
+ }
2528
+ // either use setOutputToNewReadable XOR setOutputWriter; not both
2529
+ setOutputWriter(writeFn) {
2530
+ this.writeFn = writeFn;
2531
+ }
2532
+ emitInput(input) {
2533
+ if (this.writeFn) {
2534
+ this.writeFn(input);
2535
+ }
2536
+ }
2537
+ end() {
2538
+ if (this.inputStreamForRegexMatcher) {
2539
+ this.inputStreamForRegexMatcher.push(null);
2540
+ }
2541
+ }
2542
+ };
2543
+
2544
+ // src/app.ts
2545
+ import * as util2 from "util";
2546
+ import chalk4 from "chalk";
2547
+ import { password as promptPassword } from "@inquirer/prompts";
2548
+ import { Listr } from "listr2";
2549
+
2550
+ // src/task.model.ts
2551
+ var Task = class {
2552
+ constructor(runFn, taskModuleAbsolutePath, description, name) {
2553
+ this.runFn = runFn;
2554
+ this.taskModuleAbsolutePath = taskModuleAbsolutePath;
2555
+ this.description = description;
2556
+ this.name = name;
2557
+ }
2558
+ };
2559
+
2560
+ // src/stacktrace.ts
2561
+ import StackTracey from "stacktracey";
2562
+ function callstack() {
2563
+ return new StackTracey();
2564
+ }
2565
+
2566
+ // src/runtime-base.ts
2567
+ import { Mutex as Mutex2 } from "async-mutex";
2568
+
2569
+ // src/command.ts
2570
+ import "shell-quote";
2571
+ import Shellwords from "shellwords-ts";
2572
+ var CommandResult = class {
2573
+ constructor(stdout, stderr, exitCode, signal) {
2574
+ this.stdout = stdout;
2575
+ this.stderr = stderr;
2576
+ this.exitCode = exitCode;
2577
+ this.signal = signal;
2578
+ }
2579
+ toJSON() {
2580
+ return {
2581
+ stdout: this.stdout,
2582
+ stderr: this.stderr,
2583
+ exitCode: this.exitCode,
2584
+ signal: this.signal,
2585
+ success: this.success,
2586
+ failure: this.failure
2587
+ };
2588
+ }
2589
+ get out() {
2590
+ return this.stdout;
2591
+ }
2592
+ get err() {
2593
+ return this.stderr;
2594
+ }
2595
+ get exit() {
2596
+ return this.exitCode;
2597
+ }
2598
+ get sig() {
2599
+ return this.signal;
2600
+ }
2601
+ get success() {
2602
+ return this.exitCode === 0;
2603
+ }
2604
+ get failure() {
2605
+ return !this.success;
2606
+ }
2607
+ };
2608
+ var Command = class {
2609
+ static parse(commandString, env) {
2610
+ if (!commandString || commandString.trim() === "") {
2611
+ throw new Error("Command string cannot be empty.");
2612
+ }
2613
+ const parts = Shellwords.split(commandString);
2614
+ const cmd_unsafe = A2.head(parts);
2615
+ if (cmd_unsafe === void 0) {
2616
+ throw new Error(`Failed to parse command from string: "${commandString}". Resulted in no command part.`);
2617
+ }
2618
+ const cmd = cmd_unsafe;
2619
+ const parsedArgs = A2(parts).skipFirst(1);
2620
+ const args = A2(parsedArgs).compact();
2621
+ return {
2622
+ cmd,
2623
+ args,
2624
+ env
2625
+ };
2626
+ }
2627
+ cmd;
2628
+ args;
2629
+ cwd;
2630
+ env;
2631
+ result;
2632
+ constructor(opts) {
2633
+ const { cmd, args, cwd, env, result } = opts;
2634
+ this.cmd = cmd;
2635
+ this.args = args;
2636
+ this.cwd = cwd;
2637
+ this.env = env;
2638
+ this.result = result;
2639
+ }
2640
+ isRunning() {
2641
+ return !!this.result;
2642
+ }
2643
+ toJSON() {
2644
+ return {
2645
+ cmd: this.cmd,
2646
+ args: this.args,
2647
+ cwd: this.cwd,
2648
+ env: this.env,
2649
+ result: this.result?.toJSON()
2650
+ };
2651
+ }
2652
+ };
2653
+
2654
+ // src/runtime-base.ts
2655
+ import { StringBuilder } from "typescript-string-operations";
2656
+ import chalk from "chalk";
2657
+ function task(runFn, options) {
2658
+ const moduleThatTaskIsDefinedIn = Path.new(callstack().items[2].file).absolute().toString();
2659
+ const taskName = options?.name || "anonymous_task";
2660
+ const taskInstance = new Task(runFn, moduleThatTaskIsDefinedIn, options?.description, taskName);
2661
+ const taskFnObject = function(params) {
2662
+ return function(parentInvocation) {
2663
+ return parentInvocation.invokeChildTask(taskFnObject, params ?? {});
2664
+ };
2665
+ };
2666
+ Object.assign(taskFnObject, { task: taskInstance });
2667
+ return taskFnObject;
2668
+ }
2669
+ var Invocation = class {
2670
+ constructor(runtime, taskFnDefinition, parent) {
2671
+ this.runtime = runtime;
2672
+ this.taskFnDefinition = taskFnDefinition;
2673
+ this.parent = parent;
2674
+ this.id = crypto.randomUUID();
2675
+ this.children = [];
2676
+ this.childrenMutex = new Mutex2();
2677
+ const { promise, resolve, reject } = Promise.withResolvers();
2678
+ this.result = promise;
2679
+ this.resolveResult = resolve;
2680
+ this.rejectResult = reject;
2681
+ this.stdoutSB = new StringBuilder();
2682
+ this.stderrSB = new StringBuilder();
2683
+ }
2684
+ id;
2685
+ children;
2686
+ // Children can have any params
2687
+ childrenMutex;
2688
+ description;
2689
+ stderrSB;
2690
+ stdoutSB;
2691
+ host;
2692
+ result;
2693
+ resolveResult;
2694
+ rejectResult;
2695
+ // Constructor and fields are above
2696
+ // Concrete method implementations
2697
+ getPassword() {
2698
+ return this.runtime.getPassword();
2699
+ }
2700
+ async getSecret(name) {
2701
+ const secret = this.runtime.app.config.getSecret(name);
2702
+ if (!secret) {
2703
+ return void 0;
2704
+ }
2705
+ return secret.plaintext();
2706
+ }
2707
+ get stderr() {
2708
+ return this.stderrSB.toString();
2709
+ }
2710
+ get stdout() {
2711
+ return this.stdoutSB.toString();
2712
+ }
2713
+ info(...message) {
2714
+ this.log(Verbosity.INFO, ...message);
2715
+ }
2716
+ debug(...message) {
2717
+ this.log(Verbosity.DEBUG, ...message);
2718
+ }
2719
+ warn(...message) {
2720
+ this.log(Verbosity.WARN, ...message);
2721
+ }
2722
+ error(...message) {
2723
+ this.log(Verbosity.ERROR, ...message);
2724
+ }
2725
+ async resultError() {
2726
+ try {
2727
+ await this.result;
2728
+ return void 0;
2729
+ } catch (e) {
2730
+ return e;
2731
+ }
2732
+ }
2733
+ setDescription(description) {
2734
+ this.description = description;
2735
+ }
2736
+ setFutureResult(resultPromise) {
2737
+ resultPromise.then(this.resolveResult, this.rejectResult);
2738
+ }
2739
+ getDescription() {
2740
+ return this.description;
2741
+ }
2742
+ getChildren() {
2743
+ return this.children;
2744
+ }
2745
+ getResultPromise() {
2746
+ return this.result;
2747
+ }
2748
+ exit(exitCode, message) {
2749
+ if (message) {
2750
+ this.runtime.app.warn(
2751
+ chalk.yellow(`Task ${this.description || this.id} requested exit with code ${exitCode}: ${message}`)
2752
+ );
2753
+ }
2754
+ process.exit(exitCode);
2755
+ }
2756
+ inventory(tags) {
2757
+ return this.runtime.inventory(tags);
2758
+ }
2759
+ async appendChildInvocation(childInvocation) {
2760
+ await this.childrenMutex.runExclusive(async () => {
2761
+ this.children.push(childInvocation);
2762
+ });
2763
+ }
2764
+ // Note: 'exit' has a concrete implementation above, so it's not abstract here.
2765
+ // Note: 'inventory' has a concrete implementation above, so it's not abstract here.
2766
+ };
2767
+
2768
+ // src/local-runtime.ts
2769
+ import Handlebars2 from "handlebars";
2770
+ import * as fs5 from "fs";
2771
+
2772
+ // src/ruspty-command.ts
2773
+ import { Pty } from "@replit/ruspty";
2774
+ import { StringBuilder as StringBuilder2 } from "typescript-string-operations";
2775
+ import "human-signals";
2776
+ var RusPtyCommand = class _RusPtyCommand extends Command {
2777
+ static fromArray(commandWithArgs, env, cwd) {
2778
+ const envObj = O(env || {}).map(([key, value]) => [key, String(value)]);
2779
+ const cmd = commandWithArgs[0] || "";
2780
+ const parsedArgs = commandWithArgs.slice(1);
2781
+ const args = parsedArgs.filter((arg) => arg !== null && arg !== void 0 && arg !== "");
2782
+ return new _RusPtyCommand({ cmd, args, cwd, env: envObj });
2783
+ }
2784
+ static fromString(command, env, cwd) {
2785
+ const envObj = O(env || {}).map(([key, value]) => [key, String(value)]);
2786
+ const { cmd, args } = this.parse(command, envObj);
2787
+ return new _RusPtyCommand({ cmd, args, cwd, env: envObj });
2788
+ }
2789
+ process;
2790
+ // Definite assignment assertion
2791
+ async run(interactionHandler = new InteractionHandler(), stdin) {
2792
+ const self = this;
2793
+ return new Promise(function(resolve, reject) {
2794
+ const stdout = new StringBuilder2();
2795
+ self.process = new Pty({
2796
+ command: self.cmd,
2797
+ args: self.args,
2798
+ dir: self.cwd,
2799
+ envs: self.env,
2800
+ interactive: true,
2801
+ size: { rows: 30, cols: 80 },
2802
+ onExit: (err, exitCode) => {
2803
+ interactionHandler.end();
2804
+ const signalObj = void 0;
2805
+ const commandResult = new CommandResult(stdout.toString(), "", exitCode, signalObj);
2806
+ self.result = commandResult;
2807
+ resolve(commandResult);
2808
+ }
2809
+ });
2810
+ const read = self.process.read;
2811
+ const write = self.process.write;
2812
+ if (stdin) {
2813
+ write.write(stdin);
2814
+ }
2815
+ interactionHandler.begin();
2816
+ interactionHandler.setOutputWriter((str) => write.write(str));
2817
+ read.on("data", (chunk) => {
2818
+ stdout.append(chunk);
2819
+ interactionHandler.handleInput(chunk);
2820
+ });
2821
+ });
2822
+ }
2823
+ };
2824
+
2825
+ // src/local-runtime.ts
2826
+ import chalk2 from "chalk";
2827
+
2828
+ // src/util/input-helpers.ts
2829
+ var CHECKMARK = "\u2713";
2830
+ var XMARK = "\u2717";
2831
+ var SUDO_PROMPT_REGEX = {
2832
+ /**
2833
+ * Matches the default sudo prompt on most Linux distributions.
2834
+ * Example: `[sudo] password for <username>:`
2835
+ */
2836
+ LINUX: "\\[sudo\\] password",
2837
+ /**
2838
+ * Matches the default sudo prompt on macOS and some BSD systems.
2839
+ * Example: `Password:`
2840
+ */
2841
+ MAC_BSD: "^Password:",
2842
+ /**
2843
+ * Matches the default prompt for `doas` on OpenBSD.
2844
+ * Example: `doas (username@hostname) password:`
2845
+ */
2846
+ DOAS: "doas \\(.*\\) password:"
2847
+ };
2848
+ function withSudo(password, existingInputMap = {}) {
2849
+ if (!password) {
2850
+ return existingInputMap;
2851
+ }
2852
+ const passwordResponse = `${password}
2853
+ `;
2854
+ return {
2855
+ ...existingInputMap,
2856
+ [SUDO_PROMPT_REGEX.LINUX]: passwordResponse,
2857
+ [SUDO_PROMPT_REGEX.MAC_BSD]: passwordResponse,
2858
+ [SUDO_PROMPT_REGEX.DOAS]: passwordResponse
2859
+ };
2860
+ }
2861
+
2862
+ // src/local-runtime.ts
2863
+ var LocalInvocation = class _LocalInvocation extends Invocation {
2864
+ constructor(runtime, taskFnDefinition, params, parent) {
2865
+ super(runtime, taskFnDefinition, parent);
2866
+ this.runtime = runtime;
2867
+ this.taskFnDefinition = taskFnDefinition;
2868
+ this.description = Handlebars2.compile(
2869
+ taskFnDefinition.task.description || taskFnDefinition.task.name || "anonymous_local_task"
2870
+ )(params);
2871
+ this.config = this.runtime.app.config;
2872
+ this.file = {
2873
+ read: async (path3) => fs5.promises.readFile(path3, "utf-8"),
2874
+ write: async (path3, content, options) => fs5.promises.writeFile(path3, content, { mode: options?.mode }),
2875
+ exists: async (path3) => {
2876
+ try {
2877
+ await fs5.promises.access(path3);
2878
+ return true;
2879
+ } catch {
2880
+ return false;
2881
+ }
2882
+ },
2883
+ mkdir: async (path3, options) => {
2884
+ await fs5.promises.mkdir(path3, options);
2885
+ },
2886
+ rm: async (path3, options) => fs5.promises.rm(path3, options)
2887
+ };
2888
+ }
2889
+ config;
2890
+ file;
2891
+ log(level, ...message) {
2892
+ this.runtime.app.log(level, ...message);
2893
+ }
2894
+ async exec(command, options = {}) {
2895
+ options = options || {};
2896
+ const interactionHandler = this.runtime.interactionHandler.clone();
2897
+ const stdinForCommand = options.stdin;
2898
+ if (options.input && typeof options.input === "object") {
2899
+ O(options.input).each(([pattern, value]) => {
2900
+ interactionHandler.map(pattern, value);
2901
+ });
2902
+ }
2903
+ if (options.sudo) {
2904
+ const hostPassword = await this.runtime.getPassword();
2905
+ interactionHandler.mapInput(withSudo(hostPassword));
2906
+ }
2907
+ const cwd = options.cwd ?? process.cwd();
2908
+ let finalEnv = {};
2909
+ if (options.env) {
2910
+ finalEnv = options.env;
2911
+ } else {
2912
+ Object.keys(process.env).forEach((key) => {
2913
+ const value = process.env[key];
2914
+ if (value !== void 0) {
2915
+ finalEnv[key] = value;
2916
+ }
2917
+ });
2918
+ }
2919
+ let commandInstance;
2920
+ if (command instanceof Array) {
2921
+ commandInstance = RusPtyCommand.fromArray(command, finalEnv, cwd);
2922
+ } else {
2923
+ commandInstance = RusPtyCommand.fromString(command, finalEnv, cwd);
2924
+ }
2925
+ this.log(
2926
+ Verbosity.DEBUG,
2927
+ `Executing command: ${Array.isArray(command) ? command.join(" ") : command}${options.stdin ? " with stdin" : ""}${options.input ? " with interaction patterns" : ""}`
2928
+ );
2929
+ await commandInstance.run(interactionHandler, stdinForCommand);
2930
+ const cmdResult = commandInstance.result;
2931
+ if (!cmdResult) {
2932
+ this.log(Verbosity.ERROR, "Command execution resulted in undefined result.");
2933
+ return new CommandResult("", "Command execution resulted in undefined result.", -1, void 0);
2934
+ }
2935
+ return cmdResult;
2936
+ }
2937
+ async ssh(tags, remoteTaskFn) {
2938
+ const targetHosts = this.runtime.app.querySelectedInventory(tags);
2939
+ if (targetHosts.length === 0) {
2940
+ this.runtime.app.warn(`No hosts found for tags: ${tags.join(", ")} in ssh call from ${this.id}`);
2941
+ return {};
2942
+ }
2943
+ const entryPromises = VP(targetHosts).map(async (host) => {
2944
+ const result = await this.runRemoteTaskOnHost(host, remoteTaskFn);
2945
+ return [host.hostname, result];
2946
+ }).value;
2947
+ const entries = await Promise.all(entryPromises);
2948
+ const record = Object.fromEntries(entries);
2949
+ return record;
2950
+ }
2951
+ async runRemoteTaskOnHost(host, remoteTaskFn) {
2952
+ this.debug(`Run function on: ${chalk2.yellow(V.inspect(host.toObject()))}`);
2953
+ const interactionHandler = new InteractionHandler(this.runtime.app.getSecretsForHost(host.hostname));
2954
+ const remoteRuntime = new RemoteRuntime(
2955
+ this.runtime.app,
2956
+ host,
2957
+ this.runtime,
2958
+ // The LocalRuntime instance of this LocalInvocation
2959
+ interactionHandler
2960
+ );
2961
+ try {
2962
+ const connected = await remoteRuntime.connect();
2963
+ if (!connected) {
2964
+ throw new Error(`Unable to connect to ${host.uri}`);
2965
+ }
2966
+ const adHocRemoteInvocationTaskFn = task(
2967
+ async function(context) {
2968
+ return remoteTaskFn(context);
2969
+ },
2970
+ { description: `Ad-hoc task on ${host.uri} via ssh from ${this.taskFnDefinition.task.name} (${this.id})` }
2971
+ );
2972
+ const rootRemoteInvocation = new RemoteInvocation(
2973
+ remoteRuntime,
2974
+ adHocRemoteInvocationTaskFn,
2975
+ // This TaskFn is for the container of the ad-hoc operation on this host
2976
+ {},
2977
+ this,
2978
+ // Parent is the current LocalInvocation
2979
+ host
2980
+ );
2981
+ await this.appendChildInvocation(rootRemoteInvocation);
2982
+ const rootRemoteContext = this.runtime.app.taskContextForRunFn(
2983
+ rootRemoteInvocation,
2984
+ {},
2985
+ host
2986
+ );
2987
+ const resultPromise = remoteTaskFn(rootRemoteContext);
2988
+ rootRemoteInvocation.setFutureResult(resultPromise);
2989
+ rootRemoteInvocation.setDescription(`SSH to ${host.uri}`);
2990
+ return await resultPromise;
2991
+ } catch (error) {
2992
+ const errorMessage = error instanceof Error ? error.message : String(error);
2993
+ this.runtime.app.error(
2994
+ `Error during SSH operation to ${host.uri}: ${errorMessage}`,
2995
+ error instanceof Error ? error.stack : void 0
2996
+ );
2997
+ return error instanceof Error ? error : new Error(errorMessage);
2998
+ } finally {
2999
+ await remoteRuntime.disconnect();
3000
+ }
3001
+ }
3002
+ exit(exitCode, message) {
3003
+ if (message) {
3004
+ this.log(exitCode === 0 ? Verbosity.INFO : Verbosity.ERROR, message);
3005
+ }
3006
+ this.runtime.app.warn(`Task ${this.id} requested exit with code ${exitCode}. Forcing process exit.`);
3007
+ process.exit(exitCode);
3008
+ }
3009
+ inventory(tags) {
3010
+ return this.runtime.app.queryInventory(new Set(tags));
3011
+ }
3012
+ async run(taskPartialFn) {
3013
+ return taskPartialFn(this);
3014
+ }
3015
+ async invokeChildTask(childTaskFnDefinition, params) {
3016
+ const childInvocation = new _LocalInvocation(this.runtime, childTaskFnDefinition, params, this);
3017
+ await this.appendChildInvocation(childInvocation);
3018
+ const loggedChildResultPromise = childInvocation.result.then((value) => {
3019
+ if (value instanceof Error) return value;
3020
+ if (value === void 0 || value === null) return void 0;
3021
+ return String(value);
3022
+ }).catch((error) => error);
3023
+ this.runtime.app.taskTree.add(
3024
+ childInvocation.id,
3025
+ this.id,
3026
+ childTaskFnDefinition.task.name || "anonymous_child_task",
3027
+ loggedChildResultPromise
3028
+ );
3029
+ const taskContext = this.runtime.app.taskContextForRunFn(
3030
+ childInvocation,
3031
+ params,
3032
+ void 0
3033
+ );
3034
+ try {
3035
+ const result = await childTaskFnDefinition.task.runFn.call(childInvocation, taskContext);
3036
+ childInvocation.resolveResult(result);
3037
+ return result;
3038
+ } catch (error) {
3039
+ childInvocation.rejectResult(error);
3040
+ throw error;
3041
+ }
3042
+ }
3043
+ };
3044
+ var LocalRuntime = class {
3045
+ constructor(app, localBundlePath, interactionHandler = new InteractionHandler()) {
3046
+ this.app = app;
3047
+ this.localBundlePath = localBundlePath;
3048
+ this.interactionHandler = interactionHandler;
3049
+ const appConfigInstance = this.app.config;
3050
+ if (appConfigInstance instanceof ConfigFile2) {
3051
+ this.host = new Host(appConfigInstance, { hostname: "localhost" });
3052
+ } else {
3053
+ const configType = appConfigInstance?.constructor?.name || typeof appConfigInstance;
3054
+ this.app.error(
3055
+ `CRITICAL ERROR: LocalRuntime could not initialize its 'localhost' Host object. The application's configuration (type: ${configType}) is not a file-based configuration (expected ConfigFile).`
3056
+ );
3057
+ throw new Error(
3058
+ `LocalRuntime init failed: Expected app.config to be an instance of ConfigFile for 'localhost' Host, but got ${configType}.`
3059
+ );
3060
+ }
3061
+ this.config = {
3062
+ cwd: process.cwd(),
3063
+ configFile: appConfigInstance instanceof ConfigFile2 ? appConfigInstance : void 0
3064
+ };
3065
+ }
3066
+ config;
3067
+ host;
3068
+ // Represents the local machine
3069
+ memoizedPassword;
3070
+ get tmpDirRootPath() {
3071
+ return this.app.hostctlTmpDir();
3072
+ }
3073
+ async getPassword() {
3074
+ if (this.memoizedPassword) {
3075
+ return this.memoizedPassword;
3076
+ }
3077
+ this.memoizedPassword = await this.app.promptPassword("Enter local sudo password:");
3078
+ return this.memoizedPassword;
3079
+ }
3080
+ async getSecret(name) {
3081
+ const secret = this.app.config.getSecret(name);
3082
+ if (!secret) {
3083
+ this.app.warn(`Secret ${name} not found in config.`);
3084
+ return void 0;
3085
+ }
3086
+ return secret.plaintext();
3087
+ }
3088
+ getTmpDir() {
3089
+ return this.app.createNamedTmpDir();
3090
+ }
3091
+ getTmpFile(prefix, postfix, dir) {
3092
+ const tmpDir = dir || this.getTmpDir();
3093
+ const randomName = prefix ? `${prefix}-${crypto.randomUUID()}` : crypto.randomUUID();
3094
+ const finalName = postfix ? `${randomName}-${postfix}` : randomName;
3095
+ return tmpDir.join(finalName);
3096
+ }
3097
+ inventory(tags) {
3098
+ return this.app.queryInventory(new Set(tags));
3099
+ }
3100
+ async invokeRootTask(taskFnDefinition, params, description) {
3101
+ const rootInvocation = new LocalInvocation(
3102
+ this,
3103
+ taskFnDefinition,
3104
+ params,
3105
+ void 0
3106
+ );
3107
+ if (description) {
3108
+ rootInvocation.description = description;
3109
+ }
3110
+ const loggedRootResultPromise = rootInvocation.result.then((value) => {
3111
+ if (value instanceof Error) return value;
3112
+ if (value === void 0 || value === null) return void 0;
3113
+ return String(value);
3114
+ }).catch((error) => error);
3115
+ this.app.taskTree.add(
3116
+ rootInvocation.id,
3117
+ null,
3118
+ taskFnDefinition.task.name || "anonymous_root_task",
3119
+ loggedRootResultPromise
3120
+ );
3121
+ const taskContext = this.app.taskContextForRunFn(
3122
+ rootInvocation,
3123
+ params,
3124
+ void 0
3125
+ );
3126
+ taskFnDefinition.task.runFn.call(rootInvocation, taskContext).then(rootInvocation.resolveResult).catch(rootInvocation.rejectResult);
3127
+ return rootInvocation;
3128
+ }
3129
+ };
3130
+
3131
+ // src/remote-runtime.ts
3132
+ import Handlebars3 from "handlebars";
3133
+ import "path";
3134
+
3135
+ // src/ssh-session.ts
3136
+ import { signalsByName } from "human-signals";
3137
+ import { NodeSSH } from "node-ssh";
3138
+ import { PassThrough } from "stream";
3139
+ var SSHSession = class {
3140
+ ssh;
3141
+ constructor() {
3142
+ this.ssh = new NodeSSH();
3143
+ }
3144
+ connect(sshConnectOpts) {
3145
+ return this.ssh.connect(sshConnectOpts);
3146
+ }
3147
+ async deleteFile(remotePath) {
3148
+ const self = this;
3149
+ return new Promise(function(resolve, reject) {
3150
+ self.ssh.withSFTP(async (sftp) => {
3151
+ sftp.unlink(remotePath, (err) => {
3152
+ if (err) {
3153
+ const errnoError = err;
3154
+ if (errnoError.errno === 2 || errnoError.code === "ENOENT") {
3155
+ resolve(true);
3156
+ } else {
3157
+ const errnoError2 = err;
3158
+ const errorCodeDisplay = errnoError2.code !== void 0 ? errnoError2.code : errnoError2.errno !== void 0 ? errnoError2.errno : "N/A";
3159
+ reject(`${errnoError2.message} (code ${errorCodeDisplay}) ${remotePath}`);
3160
+ }
3161
+ } else {
3162
+ resolve(true);
3163
+ }
3164
+ });
3165
+ });
3166
+ });
3167
+ }
3168
+ async sftp() {
3169
+ return this.ssh.requestSFTP();
3170
+ }
3171
+ async execCommand(command, options) {
3172
+ const nodeSshCmdOptions = {
3173
+ // Using 'any' temporarily to build up, then will cast or ensure type compatibility
3174
+ cwd: options?.cwd,
3175
+ stdin: typeof options?.stdin === "string" ? options.stdin : Buffer.isBuffer(options?.stdin) ? options.stdin.toString() : void 0
3176
+ };
3177
+ if (options?.pty !== void 0) {
3178
+ nodeSshCmdOptions.options = {
3179
+ ...nodeSshCmdOptions.options || {},
3180
+ // Preserve other potential ssh2 options if they were there
3181
+ pty: options.pty
3182
+ };
3183
+ }
3184
+ const result = await this.ssh.execCommand(command, nodeSshCmdOptions);
3185
+ const exitCode = result.code === null ? -1 : result.code;
3186
+ const signalString = result.signal;
3187
+ const signalObject = signalString ? signalsByName[signalString] : void 0;
3188
+ const commandResult = new CommandResult(result.stdout, result.stderr, exitCode, signalObject);
3189
+ return new Command({ cmd: command, args: [], cwd: options?.cwd, env: options?.env, result: commandResult });
3190
+ }
3191
+ async getFile(remotePath, localPath) {
3192
+ await this.ssh.getFile(localPath, remotePath);
3193
+ }
3194
+ /**
3195
+ * Executes a command on the remote host.
3196
+ * @param command The command to execute. Can be a string or an array of strings. If an array, the first element is
3197
+ * the command and the rest are arguments. Otherwise, the string is parsed as a command.
3198
+ * @param interactionHandler The interaction handler to use.
3199
+ * @param execOptions The options for the execution.
3200
+ * @returns A promise that resolves to the result of the command.
3201
+ */
3202
+ async run(command, interactionHandler = new InteractionHandler(), execOptions = {}) {
3203
+ const self = this;
3204
+ const combinedStdin = new PassThrough();
3205
+ if (execOptions.stdin) {
3206
+ combinedStdin.write(execOptions.stdin);
3207
+ }
3208
+ interactionHandler.setOutputToNewReadable().pipe(combinedStdin);
3209
+ interactionHandler.begin();
3210
+ let cmd, args;
3211
+ if (command instanceof Array) {
3212
+ cmd = command[0] || "";
3213
+ args = command.slice(1).filter((arg) => !!arg);
3214
+ } else {
3215
+ const parsedCommand = Command.parse(command);
3216
+ cmd = parsedCommand.cmd;
3217
+ args = parsedCommand.args;
3218
+ }
3219
+ const ptyOption = execOptions.pty ?? true;
3220
+ const { stdout, stderr, code, signal } = await self.ssh.exec(cmd, args, {
3221
+ cwd: execOptions.cwd,
3222
+ execOptions: { pty: ptyOption, env: execOptions.env },
3223
+ stream: "both",
3224
+ stdin: combinedStdin,
3225
+ onStdout: (chunk) => {
3226
+ const output = chunk.toString("utf8");
3227
+ interactionHandler.handleInput(output);
3228
+ },
3229
+ onStderr: (chunk) => {
3230
+ const errorOutput = chunk.toString("utf8");
3231
+ interactionHandler.handleInput(errorOutput);
3232
+ }
3233
+ });
3234
+ interactionHandler.end();
3235
+ const signalName = signal;
3236
+ const signalObj = signalName ? signalsByName[signalName] : void 0;
3237
+ const result = new CommandResult(stdout, stderr, code || 0, signalObj);
3238
+ return new Command({ cmd, args, cwd: execOptions.cwd, env: execOptions.env, result });
3239
+ }
3240
+ async sendFile(localPath, remotePath) {
3241
+ try {
3242
+ await this.ssh.putFile(localPath, remotePath);
3243
+ return true;
3244
+ } catch (e) {
3245
+ return false;
3246
+ }
3247
+ }
3248
+ async disconnect() {
3249
+ this.ssh.dispose();
3250
+ }
3251
+ isConnected() {
3252
+ return this.ssh.isConnected();
3253
+ }
3254
+ };
3255
+
3256
+ // src/remote-runtime.ts
3257
+ import "chalk";
3258
+ import "util";
3259
+ import "typescript-string-operations";
3260
+ import { match, P } from "ts-pattern";
3261
+ var RemoteInvocation = class _RemoteInvocation extends Invocation {
3262
+ // sshSession is removed from RemoteInvocation, it will be managed by RemoteRuntime
3263
+ constructor(runtime, taskFnDefinition, params, parent, hostInstance) {
3264
+ super(runtime, taskFnDefinition, parent);
3265
+ this.taskFnDefinition = taskFnDefinition;
3266
+ this.parent = parent;
3267
+ this.hostInstance = hostInstance;
3268
+ this.description = Handlebars3.compile(
3269
+ taskFnDefinition.task.description || taskFnDefinition.task.name || "anonymous_remote_task"
3270
+ )(params);
3271
+ this.host = hostInstance || parent.host;
3272
+ if (!this.host) {
3273
+ throw new Error("RemoteInvocation must have a host.");
3274
+ }
3275
+ this.config = this.runtime.app.config;
3276
+ this.file = {
3277
+ read: async (filePath) => {
3278
+ const sftp = await this.ensureSftp();
3279
+ const remotePath = this.resolveRemotePath(filePath);
3280
+ return new Promise((resolve, reject) => {
3281
+ sftp.readFile(remotePath, (err, data) => {
3282
+ if (err) {
3283
+ return reject(err);
3284
+ }
3285
+ if (!data) {
3286
+ return reject(new Error(`SFTP readFile returned no data for ${remotePath}`));
3287
+ }
3288
+ resolve(data.toString("utf-8"));
3289
+ });
3290
+ });
3291
+ },
3292
+ write: async (filePath, content, options) => {
3293
+ const sftp = await this.ensureSftp();
3294
+ const remotePath = this.resolveRemotePath(filePath);
3295
+ await sftp.writeFile(remotePath, content, { mode: options?.mode });
3296
+ },
3297
+ exists: async (filePath) => {
3298
+ const sftp = await this.ensureSftp();
3299
+ const remotePath = this.resolveRemotePath(filePath);
3300
+ return new Promise((resolve, reject) => {
3301
+ sftp.stat(remotePath, (err, stats) => {
3302
+ if (err) {
3303
+ const sftpError = err;
3304
+ if (sftpError.code === 2 || sftpError.code === "ENOENT" || typeof err.message === "string" && (err.message.includes("No such file") || err.message.includes("No such entity"))) {
3305
+ resolve(false);
3306
+ } else {
3307
+ reject(err);
3308
+ }
3309
+ } else {
3310
+ resolve(true);
3311
+ }
3312
+ });
3313
+ });
3314
+ },
3315
+ mkdir: async (dirPath, options) => {
3316
+ const remoteDir = this.resolveRemotePath(dirPath);
3317
+ let command = `mkdir ${options?.recursive ? "-p " : ""}${this.quoteRemoteArg(remoteDir)}`;
3318
+ await this.exec(command);
3319
+ if (options?.mode) {
3320
+ await this.exec(`chmod ${this.quoteRemoteArg(String(options.mode))} ${this.quoteRemoteArg(remoteDir)}`);
3321
+ }
3322
+ },
3323
+ rm: async (itemPath, options) => {
3324
+ const remoteItem = this.resolveRemotePath(itemPath);
3325
+ const command = `rm ${options?.recursive ? "-r " : ""}${options?.force ? "-f " : ""}${this.quoteRemoteArg(remoteItem)}`;
3326
+ await this.exec(command);
3327
+ }
3328
+ };
3329
+ }
3330
+ config;
3331
+ file;
3332
+ async ensureSftp() {
3333
+ if (!(this.runtime instanceof RemoteRuntime)) {
3334
+ throw new Error("Runtime is not a RemoteRuntime instance, cannot ensure SFTP.");
3335
+ }
3336
+ return this.runtime.getSftpClient();
3337
+ }
3338
+ resolveRemotePath(p) {
3339
+ return p;
3340
+ }
3341
+ quoteRemoteArg(arg) {
3342
+ return "'" + arg.replace(/'/g, "'\\''") + "'";
3343
+ }
3344
+ log(level, ...message) {
3345
+ const hostUri = this.host ? `${this.host.user || ""}@${this.host.hostname}` : "unknown-host";
3346
+ this.runtime.app.log(level, `[${hostUri}|${this.id}|${this.description || "task"}]`, ...message);
3347
+ }
3348
+ uri() {
3349
+ if (!this.host) throw new Error("Host is not defined for this remote invocation.");
3350
+ return `${this.host.user || this.runtime.app.defaultSshUser()}@${this.host.hostname}`;
3351
+ }
3352
+ async exec(command, options) {
3353
+ if (!this.host) throw new Error("Host is not defined for exec command in RemoteInvocation.");
3354
+ if (!(this.runtime instanceof RemoteRuntime)) {
3355
+ throw new Error("Runtime is not a RemoteRuntime instance, cannot execute remote command.");
3356
+ }
3357
+ const stdinForRemoteCommand = options?.stdin;
3358
+ const execSpecificInteractionHandler = this.runtime.interactionHandler.clone();
3359
+ if (options?.input) {
3360
+ execSpecificInteractionHandler.mapInput(options.input);
3361
+ }
3362
+ if (options?.sudo) {
3363
+ const hostPassword = await this.runtime.getPassword();
3364
+ execSpecificInteractionHandler.mapInput(withSudo(hostPassword));
3365
+ }
3366
+ this.debug(`Executing remote command on ${this.host.hostname}: ${command}`);
3367
+ const cmdOrErr = await this.runtime.executeCommand(command, {
3368
+ cwd: options?.cwd,
3369
+ stdin: stdinForRemoteCommand,
3370
+ pty: options?.pty,
3371
+ env: options?.env,
3372
+ interactionHandler: execSpecificInteractionHandler
3373
+ // Pass the potentially augmented handler
3374
+ });
3375
+ return match(cmdOrErr).with(P.instanceOf(Error), (err) => {
3376
+ throw err;
3377
+ }).with(P.instanceOf(Command), (command2) => {
3378
+ return command2.result;
3379
+ }).exhaustive();
3380
+ }
3381
+ exit(exitCode, message) {
3382
+ const hostUri = this.host ? `${this.host.user || ""}@${this.host.hostname}` : "unknown-host";
3383
+ const msg = `Remote task ${this.id} on ${hostUri} requested exit with code ${exitCode}${message ? ": " + message : ""}`;
3384
+ this.log(Verbosity.WARN, msg);
3385
+ throw new Error(msg);
3386
+ }
3387
+ inventory(tags) {
3388
+ return this.runtime.app.queryInventory(new Set(tags));
3389
+ }
3390
+ async run(taskPartialFn) {
3391
+ return taskPartialFn(this);
3392
+ }
3393
+ async invokeChildTask(childTaskFnDefinition, params) {
3394
+ if (!this.host) throw new Error("Host is not defined for invokeChildTask in RemoteInvocation.");
3395
+ const childInvocation = new _RemoteInvocation(this.runtime, childTaskFnDefinition, params, this, this.host);
3396
+ await this.appendChildInvocation(childInvocation);
3397
+ this.runtime.app.taskTree.add(
3398
+ childInvocation.id,
3399
+ this.id,
3400
+ childTaskFnDefinition.task.name || "anonymous_remote_child_task",
3401
+ childInvocation.result.then((r) => r === null ? void 0 : r)
3402
+ );
3403
+ const taskContext = this.runtime.app.taskContextForRunFn(
3404
+ childInvocation,
3405
+ params,
3406
+ this.host
3407
+ );
3408
+ try {
3409
+ const result = await childTaskFnDefinition.task.runFn.call(childInvocation, taskContext);
3410
+ childInvocation.resolveResult(result);
3411
+ return result;
3412
+ } catch (error) {
3413
+ childInvocation.rejectResult(error);
3414
+ throw error;
3415
+ }
3416
+ }
3417
+ // End of invokeChildTask method
3418
+ ssh(tags, remoteTaskFn) {
3419
+ throw new Error("ssh from a remote host is not currently supported.");
3420
+ }
3421
+ };
3422
+ var RemoteRuntime = class {
3423
+ sftpClientInstance;
3424
+ // Type should be SFTPWrapper from 'node-ssh'
3425
+ app;
3426
+ host;
3427
+ interactionHandler;
3428
+ sshSession;
3429
+ constructor(app, host, _localRuntime, interactionHandler = new InteractionHandler()) {
3430
+ this.app = app;
3431
+ this.host = host;
3432
+ this.interactionHandler = interactionHandler;
3433
+ this.sshSession = new SSHSession();
3434
+ }
3435
+ get tmpDirRootPath() {
3436
+ return new Path("/tmp");
3437
+ }
3438
+ async getPassword() {
3439
+ return await this.host.decryptedPassword();
3440
+ }
3441
+ async getSecret(name) {
3442
+ const secret = this.app.config.getSecret(name);
3443
+ if (!secret) {
3444
+ this.app.warn(`Secret ${name} not found in config.`);
3445
+ return void 0;
3446
+ }
3447
+ return secret.plaintext();
3448
+ }
3449
+ getTmpDir() {
3450
+ return this.tmpDirRootPath.join(this.app.randName());
3451
+ }
3452
+ getTmpFile(prefix, postfix, dir) {
3453
+ const tmpDir = dir || this.getTmpDir();
3454
+ return tmpDir.join(this.app.randName());
3455
+ }
3456
+ inventory(tags) {
3457
+ return this.app.queryInventory(new Set(tags));
3458
+ }
3459
+ async connect() {
3460
+ if (this.sshSession.isConnected() && this.sftpClientInstance) {
3461
+ this.app.debug(`RemoteRuntime: SSH session and SFTP already connected to ${this.host.uri}`);
3462
+ return true;
3463
+ }
3464
+ this.app.debug(`RemoteRuntime: Connecting SSH to ${this.host.uri}...`);
3465
+ const sshConnectOpts = {
3466
+ host: this.host.hostname,
3467
+ // Changed from this.host.host
3468
+ username: this.host.user || this.app.defaultSshUser(),
3469
+ // Assuming defaultSshUser on App
3470
+ port: this.host.port,
3471
+ // node-ssh parses port from host string if present
3472
+ privateKey: await this.host.plaintextSshKeyPath(),
3473
+ password: await this.host.decryptedPassword()
3474
+ };
3475
+ try {
3476
+ if (!this.sshSession.isConnected()) {
3477
+ await this.sshSession.connect(sshConnectOpts);
3478
+ this.app.debug(`RemoteRuntime: SSH connected to ${this.host.uri}`);
3479
+ }
3480
+ this.app.debug(`RemoteRuntime: Requesting SFTP client for ${this.host.uri}`);
3481
+ this.sftpClientInstance = await this.sshSession.sftp();
3482
+ this.app.debug(`RemoteRuntime: SFTP client ready for ${this.host.uri}`);
3483
+ } catch (e) {
3484
+ this.app.error(`RemoteRuntime: SSH connection or SFTP setup to ${this.host.uri} failed: ${e.message}`);
3485
+ if (this.sshSession.isConnected()) {
3486
+ await this.sshSession.disconnect().catch((err) => {
3487
+ this.app.error(`RemoteRuntime: Error during disconnect after failed connect/sftp: ${err.message}`);
3488
+ });
3489
+ }
3490
+ this.sftpClientInstance = void 0;
3491
+ throw e;
3492
+ }
3493
+ return this.sshSession.isConnected();
3494
+ }
3495
+ async getSftpClient() {
3496
+ await this.connect();
3497
+ if (!this.sftpClientInstance) {
3498
+ throw new Error(`SFTP client not available on SSHSession for ${this.host.uri} after connect call.`);
3499
+ }
3500
+ return this.sftpClientInstance;
3501
+ }
3502
+ async executeCommand(command, options) {
3503
+ this.app.debug(`RemoteRuntime: Executing on ${this.host.uri}: ${command}`);
3504
+ const handlerToUse = options?.interactionHandler || this.interactionHandler;
3505
+ let cmdOrErr;
3506
+ try {
3507
+ if (options?.pty || handlerToUse !== this.interactionHandler) {
3508
+ this.app.debug(`RemoteRuntime: Executing command on ${this.host.uri} via sshSession.run (PTY/custom handler)`);
3509
+ const commandObj = await this.sshSession.run(command, handlerToUse, {
3510
+ cwd: options?.cwd,
3511
+ stdin: options?.stdin,
3512
+ pty: options?.pty,
3513
+ env: options?.env
3514
+ });
3515
+ cmdOrErr = commandObj;
3516
+ } else {
3517
+ this.app.debug(
3518
+ `RemoteRuntime: Executing command on ${this.host.uri} via sshSession.execCommand (no PTY/default handler)`
3519
+ );
3520
+ const actualCommand = Array.isArray(command) ? command.join(" ") : command;
3521
+ const commandObj = await this.sshSession.execCommand(actualCommand, {
3522
+ cwd: options?.cwd,
3523
+ stdin: options?.stdin,
3524
+ env: options?.env
3525
+ });
3526
+ cmdOrErr = commandObj;
3527
+ }
3528
+ this.app.logHostCommandResult(this.host, command, cmdOrErr);
3529
+ } catch (e) {
3530
+ this.app.error(`RemoteRuntime: executeCommand failed for '${command}': ${e.message}`);
3531
+ this.app.error(e.stack);
3532
+ cmdOrErr = e;
3533
+ this.app.logHostCommandResult(this.host, command, cmdOrErr);
3534
+ }
3535
+ return cmdOrErr;
3536
+ }
3537
+ async disconnect() {
3538
+ if (this.sshSession.isConnected()) {
3539
+ this.app.debug(`RemoteRuntime: Disconnecting SSH from ${this.host.uri}`);
3540
+ this.sftpClientInstance = void 0;
3541
+ await this.sshSession.disconnect();
3542
+ }
3543
+ }
3544
+ async invokeRootTask(taskFnDefinition, params) {
3545
+ await this.connect();
3546
+ if (!this.sshSession) throw new Error("SSH session not established for invokeRootTask.");
3547
+ const rootInvocation = new RemoteInvocation(
3548
+ this,
3549
+ taskFnDefinition,
3550
+ params,
3551
+ { runtime: this, host: this.host },
3552
+ this.host
3553
+ );
3554
+ this.app.taskTree.add(
3555
+ rootInvocation.id,
3556
+ null,
3557
+ taskFnDefinition.task.name || `remote_root_task_on_${this.host.hostname}`,
3558
+ // Changed host to hostname
3559
+ rootInvocation.result.then((r) => r === null ? void 0 : r)
3560
+ );
3561
+ const taskContext = this.app.taskContextForRunFn(rootInvocation, params, this.host);
3562
+ taskFnDefinition.task.runFn.call(rootInvocation, taskContext).then(rootInvocation.resolveResult).catch(rootInvocation.rejectResult);
3563
+ return rootInvocation;
3564
+ }
3565
+ };
3566
+
3567
+ // src/node-runtime.ts
3568
+ import os2 from "os";
3569
+ import axios from "axios";
3570
+ import * as cheerio from "cheerio";
3571
+ import { match as match3 } from "ts-pattern";
3572
+ import which from "which";
3573
+
3574
+ // src/http.ts
3575
+ import { createWriteStream, unlink } from "fs";
3576
+ import { get } from "https";
3577
+ async function downloadFile(url, dest) {
3578
+ return new Promise((resolve, reject) => {
3579
+ const file = createWriteStream(dest, { flags: "wx" });
3580
+ const request = get(url, (response) => {
3581
+ if (response.statusCode === 200) {
3582
+ response.pipe(file);
3583
+ } else {
3584
+ file.close();
3585
+ unlink(dest, () => {
3586
+ });
3587
+ reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
3588
+ }
3589
+ });
3590
+ request.on("error", (err) => {
3591
+ file.close();
3592
+ unlink(dest, () => {
3593
+ });
3594
+ reject(err.message);
3595
+ });
3596
+ file.on("finish", () => {
3597
+ resolve(dest.toString());
3598
+ });
3599
+ file.on("error", (err) => {
3600
+ file.close();
3601
+ if (err.code === "EEXIST") {
3602
+ reject(new Error("File already exists"));
3603
+ } else {
3604
+ unlink(dest, () => {
3605
+ });
3606
+ reject(err.message);
3607
+ }
3608
+ });
3609
+ });
3610
+ }
3611
+
3612
+ // src/shell-command.ts
3613
+ import spawnAsync2 from "@expo/spawn-async";
3614
+ import { signalsByName as signalsByName2 } from "human-signals";
3615
+ var ShellCommand = class _ShellCommand extends Command {
3616
+ static fromString(command, env, cwd) {
3617
+ const { cmd, args } = this.parse(command, env);
3618
+ return new _ShellCommand({ cmd, args, cwd, env });
3619
+ }
3620
+ async run() {
3621
+ try {
3622
+ const resultPromise = spawnAsync2(this.cmd, this.args, {
3623
+ cwd: this.cwd,
3624
+ env: this.env
3625
+ // shell: true
3626
+ });
3627
+ let { pid, stdout, stderr, status, signal } = await resultPromise;
3628
+ const signalObj = signal && signalsByName2[signal] || void 0;
3629
+ const commandResult = new CommandResult(stdout || "", stderr || "", status || 0, signalObj);
3630
+ this.result = commandResult;
3631
+ } catch (e) {
3632
+ const error = e;
3633
+ if (error.message) console.error(error.message);
3634
+ if (error.stack) console.error(error.stack);
3635
+ let { pid, stdout, stderr, status, signal } = error;
3636
+ const signalObj = signal && signalsByName2[signal] || void 0;
3637
+ const commandResult = new CommandResult(stdout || "", stderr || "", status || 1, signalObj);
3638
+ this.result = commandResult;
3639
+ }
3640
+ return this.result;
3641
+ }
3642
+ };
3643
+
3644
+ // src/unarchive.ts
3645
+ import decompress from "decompress";
3646
+ import decompressTarGzPlugin from "decompress-targz";
3647
+ import decompressZipPlugin from "decompress-unzip";
3648
+ import { match as match2, P as P2 } from "ts-pattern";
3649
+ import decompressTarPlugin from "decompress-tar";
3650
+ import { fileTypeFromBuffer } from "file-type";
3651
+ import { getStreamAsBuffer } from "get-stream";
3652
+ import xzdecompress from "xz-decompress";
3653
+ async function unarchive(inputPath, outputPath) {
3654
+ const filename = File.basename(inputPath);
3655
+ 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(() => {
3656
+ throw new Error(`unable to decompress unknown file type: ${inputPath}`);
3657
+ });
3658
+ }
3659
+ async function decompressTarGz(inputPath, outputPath, dropRootDir = 1) {
3660
+ await decompress(inputPath, outputPath, {
3661
+ plugins: [decompressTarGzPlugin()],
3662
+ strip: dropRootDir
3663
+ });
3664
+ }
3665
+ async function decompressTarXzPlugin(input) {
3666
+ const type = await fileTypeFromBuffer(input);
3667
+ if (!type || type.ext !== "xz") {
3668
+ return [];
3669
+ }
3670
+ const blob = new Blob([input]);
3671
+ const webReadableStream = blob.stream();
3672
+ const xzStream = new xzdecompress.XzReadableStream(webReadableStream);
3673
+ const decompressedBuffer = await getStreamAsBuffer(xzStream);
3674
+ return decompressTarPlugin()(decompressedBuffer);
3675
+ }
3676
+ async function decompressTarXz(inputPath, outputPath, dropRootDir = 1) {
3677
+ await decompress(inputPath, outputPath, {
3678
+ plugins: [decompressTarXzPlugin],
3679
+ strip: dropRootDir
3680
+ });
3681
+ }
3682
+ async function decompressZip(inputPath, outputPath, dropRootDir = 1) {
3683
+ await decompress(inputPath, outputPath, {
3684
+ plugins: [decompressZipPlugin()],
3685
+ strip: dropRootDir
3686
+ });
3687
+ }
3688
+
3689
+ // src/node-runtime.ts
3690
+ var NodeRuntime = class _NodeRuntime {
3691
+ constructor(tmpDir) {
3692
+ this.tmpDir = tmpDir;
3693
+ this.client = axios.create({
3694
+ baseURL: "https://nodejs.org/"
3695
+ });
3696
+ this.alreadyInstalled = false;
3697
+ this.localNode = this.tmpDir.join(_NodeRuntime.Name, "bin", "node");
3698
+ this.localNpm = this.tmpDir.join(_NodeRuntime.Name, "bin", "npm");
3699
+ }
3700
+ static Name = "nodejs";
3701
+ client;
3702
+ alreadyInstalled;
3703
+ localNode;
3704
+ localNpm;
3705
+ async isNodeInstalledGlobally() {
3706
+ const result = await ShellCommand.fromString(`node --version`).run();
3707
+ return result.success;
3708
+ }
3709
+ async isNodeInstalledLocally() {
3710
+ const result = await ShellCommand.fromString(`${this.localNode} --version`).run();
3711
+ return result.success;
3712
+ }
3713
+ async isNpmInstalledGlobally() {
3714
+ const result = await ShellCommand.fromString(`npm --version`).run();
3715
+ return result.success;
3716
+ }
3717
+ async isNpmInstalledLocally() {
3718
+ const result = await ShellCommand.fromString(`${this.localNpm} --version`).run();
3719
+ return result.success;
3720
+ }
3721
+ async nodeCmd() {
3722
+ if (await this.isNodeInstalledGlobally()) return "node";
3723
+ if (await this.isNodeInstalledLocally()) return this.localNode.toString();
3724
+ throw new Error("node not installed");
3725
+ }
3726
+ async nodePath() {
3727
+ if (await this.isNodeInstalledGlobally()) {
3728
+ return await which("node", { nothrow: true });
3729
+ }
3730
+ if (await this.isNodeInstalledLocally()) return this.localNode.toString();
3731
+ return null;
3732
+ }
3733
+ async npmCmd() {
3734
+ if (await this.isNpmInstalledGlobally()) return "npm";
3735
+ if (await this.isNpmInstalledLocally()) return this.localNpm.toString();
3736
+ throw new Error("npm not installed");
3737
+ }
3738
+ async npmPath() {
3739
+ if (await this.isNpmInstalledGlobally()) {
3740
+ return await which("npm", { nothrow: true });
3741
+ }
3742
+ if (await this.isNpmInstalledLocally()) return this.localNpm.toString();
3743
+ return null;
3744
+ }
3745
+ async installIfNeeded() {
3746
+ if (this.alreadyInstalled || await this.isNodeInstalledGlobally() || await this.isNodeInstalledLocally()) {
3747
+ this.alreadyInstalled = true;
3748
+ return;
3749
+ }
3750
+ return this.install();
3751
+ }
3752
+ async install() {
3753
+ const platform2 = os2.platform();
3754
+ const arch = os2.arch();
3755
+ const packagePath = await this.downloadNodePackage(platform2, arch);
3756
+ const unzipDir = await this.unzipPackage(packagePath);
3757
+ this.alreadyInstalled = true;
3758
+ return unzipDir;
3759
+ }
3760
+ async listPackages() {
3761
+ const response = await this.client.get(`/dist/latest/`);
3762
+ const doc = await cheerio.load(response.data);
3763
+ const allFileLinks = doc("a").map((i, el) => doc(el).attr("href")).toArray();
3764
+ const archiveFiles = A2(allFileLinks).select((filename) => filename.match(/.*\.(gz|zip|xz)/));
3765
+ const urls = A2(archiveFiles).map((filename) => `https://nodejs.org/dist/latest/${filename}`);
3766
+ return urls;
3767
+ }
3768
+ // returns the path to the downloaded zip file
3769
+ async downloadNodePackage(platform2, arch) {
3770
+ const platformInFilename = match3(platform2).with("linux", () => "linux").with("win32", () => "win").with("darwin", () => "darwin").otherwise(() => "unknown-platform");
3771
+ const archInFilename = match3(arch).with("x64", () => "x64").with("x86", () => "x86").with("arm", () => "armv7l").with("arm64", () => "arm64").otherwise(() => "unknown-arch");
3772
+ const packages = await this.listPackages();
3773
+ const url = A2(packages).find((url2) => url2.match(`node-v.*-${platformInFilename}-${archInFilename}`));
3774
+ if (V.isAbsent(url)) {
3775
+ throw new Error(`Unable to download node for ${os2}/${arch} OS/architecture`);
3776
+ }
3777
+ const filename = File.basename(url);
3778
+ const path3 = this.tmpDir.join(filename);
3779
+ if (path3.exists()) return path3.toString();
3780
+ return await downloadFile(url, path3.toString());
3781
+ }
3782
+ // returns the path to the unzipped package directory
3783
+ async unzipPackage(packagePath) {
3784
+ const dir = this.tmpDir.join(_NodeRuntime.Name);
3785
+ if (dir.exists()) return dir.toString();
3786
+ await unarchive(packagePath, dir.toString());
3787
+ return dir.toString();
3788
+ }
3789
+ async npmInstall(omitDev = true, cwd) {
3790
+ if (omitDev) {
3791
+ return this.npm("install --omit=dev", cwd);
3792
+ } else {
3793
+ return this.npm("install", cwd);
3794
+ }
3795
+ }
3796
+ async npm(npmArgs, cwd) {
3797
+ const npmCmd = await this.npmCmd();
3798
+ const env = O(process.env).select(([_, v]) => !!v);
3799
+ return ShellCommand.fromString(`${npmCmd} ${npmArgs}`.trim(), env, cwd).run();
3800
+ }
3801
+ async node(nodeArgs, cwd) {
3802
+ const nodeCmd = await this.nodeCmd();
3803
+ const env = O(process.env).select(([_, v]) => !!v);
3804
+ return ShellCommand.fromString(`${nodeCmd} ${nodeArgs}`.trim(), env, cwd).run();
3805
+ }
3806
+ };
3807
+
3808
+ // src/zip.ts
3809
+ import AdmZip from "adm-zip";
3810
+ async function zipDirectory(sourceDir, outputFilePath) {
3811
+ const zip2 = new AdmZip();
3812
+ zip2.addLocalFolder(sourceDir);
3813
+ await zip2.writeZipPromise(outputFilePath);
3814
+ return outputFilePath;
3815
+ }
3816
+ async function unzipDirectory(inputFilePath, outputDirectory) {
3817
+ const zip2 = new AdmZip(inputFilePath);
3818
+ return new Promise((resolve, reject) => {
3819
+ zip2.extractAllToAsync(outputDirectory, true, false, (error) => {
3820
+ if (error) {
3821
+ reject(error);
3822
+ } else {
3823
+ resolve(outputDirectory);
3824
+ }
3825
+ });
3826
+ });
3827
+ }
3828
+
3829
+ // src/hash.ts
3830
+ import { createHash } from "crypto";
3831
+ function sha256(str) {
3832
+ return createHash("sha256").update(str).digest("hex");
3833
+ }
3834
+
3835
+ // src/param-map.ts
3836
+ import { match as match4 } from "ts-pattern";
3837
+ var ParamMap = class _ParamMap {
3838
+ static parse(cliArguments) {
3839
+ const root = new _ParamMap();
3840
+ A2(cliArguments).each((arg) => {
3841
+ const idx = arg.indexOf(":");
3842
+ if (idx === -1) {
3843
+ root.set(arg, "true");
3844
+ return;
3845
+ }
3846
+ const key = arg.slice(0, idx);
3847
+ const value = arg.slice(idx + 1);
3848
+ root.set(key, value);
3849
+ });
3850
+ return root;
3851
+ }
3852
+ map;
3853
+ constructor() {
3854
+ this.map = /* @__PURE__ */ new Map();
3855
+ }
3856
+ get(keyPath) {
3857
+ try {
3858
+ return A2(keyPath.split(":")).reduce(
3859
+ (node, key) => node.get(key),
3860
+ this.map
3861
+ );
3862
+ } catch (e) {
3863
+ return void 0;
3864
+ }
3865
+ }
3866
+ set(keyPath, value) {
3867
+ const keyParts = keyPath.split(":");
3868
+ const paramMap = A2(keyParts).skipLast(1).reduce((node, key2) => {
3869
+ let nextNode = node.get(key2);
3870
+ if (nextNode) return nextNode;
3871
+ const newParamMap = new _ParamMap();
3872
+ node.set(key2, newParamMap);
3873
+ return newParamMap;
3874
+ }, this.map);
3875
+ const key = A2(keyParts).last(1)[0];
3876
+ paramMap.set(key, value);
3877
+ }
3878
+ toObject() {
3879
+ let obj = {};
3880
+ M(this.map).each(([k, v]) => {
3881
+ obj[k] = match4(v).when(V.isA(String), (str) => this.stringToJsonObj(str)).otherwise(() => {
3882
+ return v.toObject();
3883
+ });
3884
+ });
3885
+ return obj;
3886
+ }
3887
+ stringToJsonObj(str) {
3888
+ return match4(str).when(matches(/(.*,)+/g), (str2) => {
3889
+ return VP(str2.split(",")).map((s) => s.trim()).compact([""]).map((s) => this.stringToJsonObj(s)).value;
3890
+ }).when(isNumeric, (str2) => parseFloat(str2)).otherwise(() => str);
3891
+ }
3892
+ };
3893
+
3894
+ // src/version.ts
3895
+ var version = "0.1.32";
3896
+
3897
+ // src/app.ts
3898
+ import { retryUntilDefined } from "ts-retry";
3899
+ import { Mutex as Mutex3 } from "async-mutex";
3900
+ import { match as match5 } from "ts-pattern";
3901
+
3902
+ // src/core/remote/runAllRemote.ts
3903
+ async function run(context) {
3904
+ const { params, ssh } = context;
3905
+ const { taskFn, params: taskParams } = params;
3906
+ return await ssh([], async (remoteContext) => {
3907
+ const hostIdentifier = remoteContext.host?.alias || remoteContext.host?.shortName || "unknown_remote_host";
3908
+ remoteContext.debug(`[${hostIdentifier}] Connected via SSH.`);
3909
+ try {
3910
+ const result = await remoteContext.run(taskFn(taskParams));
3911
+ remoteContext.debug(`[${hostIdentifier}] Remote task result:`, JSON.stringify(result));
3912
+ return result;
3913
+ } catch (e) {
3914
+ remoteContext.debug(`[${hostIdentifier}] Failed to run remote task:`, e.message);
3915
+ return e;
3916
+ }
3917
+ });
3918
+ }
3919
+ var runAllRemote_default = task(run, {
3920
+ description: "run a task on all selected hosts"
3921
+ });
3922
+
3923
+ // src/app.ts
3924
+ var TaskTree = class {
3925
+ // private taskEventBus: Emittery<{ newTask: NewTaskEvent; taskComplete: TaskCompleteEvent }>;
3926
+ listr;
3927
+ subLists;
3928
+ nodes;
3929
+ mutex;
3930
+ constructor() {
3931
+ this.mutex = new Mutex3();
3932
+ this.nodes = /* @__PURE__ */ new Map();
3933
+ this.subLists = /* @__PURE__ */ new Map();
3934
+ this.listr = new Listr([], {
3935
+ // exitOnError: false,
3936
+ concurrent: true,
3937
+ rendererOptions: { collapseSubtasks: false }
3938
+ });
3939
+ }
3940
+ clear() {
3941
+ this.nodes.clear();
3942
+ this.subLists.clear();
3943
+ this.listr = new Listr([], {
3944
+ // exitOnError: false,
3945
+ concurrent: true,
3946
+ rendererOptions: { collapseSubtasks: false }
3947
+ });
3948
+ }
3949
+ async add(id, parentId, name, result) {
3950
+ return await this.mutex.runExclusive(async () => {
3951
+ const parent = this.get(parentId);
3952
+ const newNode = { id, parentId, parent, children: /* @__PURE__ */ new Set(), name, result };
3953
+ this.nodes.set(id, newNode);
3954
+ this.listr.add({
3955
+ title: newNode.name,
3956
+ task: async (ctx, task3) => {
3957
+ await result;
3958
+ }
3959
+ });
3960
+ return newNode;
3961
+ });
3962
+ }
3963
+ get(id) {
3964
+ return this.nodes.get(id);
3965
+ }
3966
+ async getWithTimeout(id) {
3967
+ try {
3968
+ return await retryUntilDefined(
3969
+ () => {
3970
+ return this.get(id);
3971
+ },
3972
+ { delay: 50, maxTry: 5 }
3973
+ );
3974
+ } catch (err) {
3975
+ return void 0;
3976
+ }
3977
+ }
3978
+ // setResult(id: string, result: Promise<string | Error | undefined>): TaskTreeNode | undefined {
3979
+ // const node = this.get(id);
3980
+ // if (node) {
3981
+ // node.result = result;
3982
+ // }
3983
+ // return node;
3984
+ // }
3985
+ run(ctx) {
3986
+ return this.listr.run(ctx);
3987
+ }
3988
+ // emit(name: any, event: any) {
3989
+ // this.taskEventBus.emit(name, event);
3990
+ // }
3991
+ };
3992
+ var Verbosity = {
3993
+ ERROR: 0,
3994
+ WARN: 1,
3995
+ INFO: 2,
3996
+ DEBUG: 3,
3997
+ TRACE: 4
3998
+ };
3999
+ var App = class {
4000
+ configRef;
4001
+ _config;
4002
+ selectedTags;
4003
+ outputStyle;
4004
+ _tmpDir;
4005
+ tmpFileRegistry;
4006
+ taskTree;
4007
+ verbosity = Verbosity.ERROR;
4008
+ constructor() {
4009
+ this.taskTree = new TaskTree();
4010
+ this.selectedTags = /* @__PURE__ */ new Set([]);
4011
+ this.outputStyle = "plain";
4012
+ this.tmpFileRegistry = new TmpFileRegistry(this.hostctlTmpDir());
4013
+ this.configRef = void 0;
4014
+ process3.on("exit", (code) => this.appExitCallback());
4015
+ }
4016
+ appExitCallback() {
4017
+ }
4018
+ get config() {
4019
+ if (!this._config) {
4020
+ throw new Error("Configuration has not been loaded. Call loadConfig() first.");
4021
+ }
4022
+ return this._config;
4023
+ }
4024
+ get tmpDir() {
4025
+ if (!this._tmpDir) {
4026
+ if (!fs6.existsSync(this.hostctlDir().toString())) {
4027
+ fs6.mkdirSync(this.hostctlDir().toString(), { recursive: true });
4028
+ }
4029
+ this._tmpDir = this.createNamedTmpDir(version);
4030
+ }
4031
+ return this._tmpDir;
4032
+ }
4033
+ async loadConfig(configString) {
4034
+ this.configRef = configString;
4035
+ this._config = await load(configString);
4036
+ }
4037
+ log(level, ...args) {
4038
+ if (this.outputStyle === "plain" && this.verbosity >= level) {
4039
+ console.log(...args);
4040
+ }
4041
+ }
4042
+ debug(...args) {
4043
+ this.log(Verbosity.DEBUG, ...args);
4044
+ }
4045
+ info(...args) {
4046
+ this.log(Verbosity.INFO, ...args);
4047
+ }
4048
+ warn(...args) {
4049
+ this.log(Verbosity.WARN, ...args);
4050
+ }
4051
+ error(...args) {
4052
+ this.log(Verbosity.ERROR, ...args);
4053
+ }
4054
+ osHomeDir() {
4055
+ return Path.new(homedir3()).absolute();
4056
+ }
4057
+ hostctlDir() {
4058
+ return this.osHomeDir().join(".hostctl");
4059
+ }
4060
+ hostctlTmpDir() {
4061
+ return this.hostctlDir().join("tmp");
4062
+ }
4063
+ randName() {
4064
+ return Math.random().toString(36).slice(-5) + Math.random().toString(36).slice(-5);
4065
+ }
4066
+ hostctlTmpPath(tmpName) {
4067
+ tmpName ??= this.randName();
4068
+ return this.hostctlTmpDir().join(tmpName);
4069
+ }
4070
+ // this directory will be automatically cleaned up at program exit
4071
+ createNamedTmpDir(subDirName) {
4072
+ return this.tmpFileRegistry.createNamedTmpDir(subDirName);
4073
+ }
4074
+ // this file will be automatically cleaned up at program exit
4075
+ writeTmpFile(fileContent) {
4076
+ return this.tmpFileRegistry.writeTmpFile(fileContent);
4077
+ }
4078
+ shouldRunRemote() {
4079
+ const selectedHosts = this.selectedInventory();
4080
+ if (selectedHosts.length === 0 && this.selectedTags.size === 0) return false;
4081
+ return selectedHosts.some((h) => !h.isLocal());
4082
+ }
4083
+ logHostCommandResult(host, command, cmdOrErr, isErrorResult = false) {
4084
+ const hostName = host.uri || (host.isLocal() ? "localhost" : "unknown-host");
4085
+ if (cmdOrErr instanceof Error || isErrorResult) {
4086
+ const error = cmdOrErr;
4087
+ this.debug(chalk4.red(`[${hostName}] Error executing command "${command}": ${error.message}`));
4088
+ if (error.stack && this.verbosity >= Verbosity.DEBUG) {
4089
+ this.debug(chalk4.red(error.stack));
4090
+ }
4091
+ } else {
4092
+ const cmdRes = cmdOrErr.result;
4093
+ this.debug(chalk4.green(`[${hostName}] Success executing "${command}" (exit code: ${cmdRes.exitCode})`));
4094
+ if (cmdRes.stdout) {
4095
+ this.debug(chalk4.cyan(`[${hostName}] STDOUT:
4096
+ ${cmdRes.stdout.trim()}`));
4097
+ }
4098
+ if (cmdRes.stderr) {
4099
+ this.debug(chalk4.yellow(`[${hostName}] STDERR:
4100
+ ${cmdRes.stderr.trim()}`));
4101
+ }
4102
+ }
4103
+ }
4104
+ defaultSshUser() {
4105
+ return void 0;
4106
+ }
4107
+ // entrypoint for the cli: AGE_IDS=~/.secrets/age/test-jill.priv tsx bin/ops.ts e whoami -t ubuntu
4108
+ // e.g. AGE_IDS=~/.secrets/age/david.priv tsx bin/ops.ts -c david.yaml exec "whoami" -t synology blog 4gb web1
4109
+ async execute(cmd) {
4110
+ const selectedHosts = this.selectedInventory();
4111
+ const cmdResults = A2(selectedHosts).map((host) => this.runRemote(host, cmd));
4112
+ const hostResultQuads = await VP(selectedHosts).a.zip(cmdResults).a.map(([host, cmdPromise]) => {
4113
+ return cmdPromise.then(
4114
+ (cmd2) => match5(cmd2).when(isError2, (err) => [host, false, "", chalk4.red(err.message)]).otherwise((cmd3) => [host, cmd3.result?.success ?? false, cmd3.result?.stdout, cmd3.result?.stderr])
4115
+ );
4116
+ }).waitAll().value;
4117
+ if (this.outputStyle === "plain") {
4118
+ this.warn(chalk4.yellow("=== Result ==="));
4119
+ A2(hostResultQuads).each(([host, success, stdout, stderr]) => {
4120
+ this.warn(
4121
+ `${success ? chalk4.green(CHECKMARK) : chalk4.red(XMARK)} ${chalk4.cyan(host.hostname)}${host.alias ? ` (${host.alias})` : ""}`
4122
+ );
4123
+ if (stdout) {
4124
+ this.warn(stdout);
4125
+ }
4126
+ if (stderr) {
4127
+ this.warn(chalk4.red(stderr));
4128
+ }
4129
+ this.warn(chalk4.yellow("--------------"));
4130
+ });
4131
+ } else if (this.outputStyle === "json") {
4132
+ const result = A2(hostResultQuads).reduce(
4133
+ (acc, [host, success, stdout, stderr]) => {
4134
+ acc[host.hostname] = {
4135
+ alias: host.alias || "",
4136
+ success,
4137
+ stdout,
4138
+ stderr
4139
+ };
4140
+ return acc;
4141
+ },
4142
+ {}
4143
+ );
4144
+ console.log(JSON.stringify(result, null, 2));
4145
+ }
4146
+ return hostResultQuads.every(([_, success]) => success);
4147
+ }
4148
+ // used by execute to run a command on the remote host
4149
+ async runRemote(host, cmd) {
4150
+ try {
4151
+ const hostPassword = await host.decryptedPassword();
4152
+ const sshConnection = {
4153
+ host: host.uri,
4154
+ username: host.user,
4155
+ password: hostPassword,
4156
+ privateKeyPath: await host.plaintextSshKeyPath()
4157
+ };
4158
+ const interactionHandler = InteractionHandler.with(withSudo(hostPassword));
4159
+ const session = new SSHSession();
4160
+ await session.connect(sshConnection);
4161
+ const commandObj = await session.run(cmd, interactionHandler);
4162
+ session.disconnect();
4163
+ return commandObj;
4164
+ } catch (e) {
4165
+ return e;
4166
+ }
4167
+ }
4168
+ getSecretsForHost(hostId) {
4169
+ const secretsMap = /* @__PURE__ */ new Map();
4170
+ if (!this._config) {
4171
+ return secretsMap;
4172
+ }
4173
+ return secretsMap;
4174
+ }
4175
+ taskContextForRunFn(invocation, params, hostForContext) {
4176
+ const effectiveHost = hostForContext || invocation.host;
4177
+ return {
4178
+ // Properties from TaskContext
4179
+ params,
4180
+ id: invocation.id,
4181
+ host: effectiveHost,
4182
+ config: invocation.config,
4183
+ // Delegated from invocation
4184
+ // Methods from TaskContext, delegating to invocation
4185
+ log: (level, ...message) => {
4186
+ invocation.log(level, ...message);
4187
+ },
4188
+ info: (...message) => {
4189
+ invocation.info(...message);
4190
+ },
4191
+ debug: (...message) => {
4192
+ invocation.debug(...message);
4193
+ },
4194
+ warn: (...message) => {
4195
+ invocation.warn(...message);
4196
+ },
4197
+ error: (...message) => {
4198
+ invocation.error(...message);
4199
+ },
4200
+ exec: async (command, options) => {
4201
+ return await invocation.exec(command, options);
4202
+ },
4203
+ ssh: async (tags, remoteTaskFn) => {
4204
+ return await invocation.ssh(tags, remoteTaskFn);
4205
+ },
4206
+ run: async (taskPartialFn) => {
4207
+ return await invocation.run(taskPartialFn);
4208
+ },
4209
+ getPassword: async () => {
4210
+ return await invocation.getPassword();
4211
+ },
4212
+ getSecret: async (name) => {
4213
+ return await invocation.getSecret(name);
4214
+ },
4215
+ exit: (exitCode, message) => {
4216
+ invocation.exit(exitCode, message);
4217
+ },
4218
+ inventory: (tags) => {
4219
+ return invocation.inventory(tags);
4220
+ },
4221
+ file: invocation.file
4222
+ };
4223
+ }
4224
+ setSelectedTags(selectedTags) {
4225
+ this.selectedTags = new Set(selectedTags);
4226
+ }
4227
+ setOutputStyle(outputStyle) {
4228
+ this.outputStyle = outputStyle;
4229
+ }
4230
+ setVerbosity(level) {
4231
+ this.verbosity = level;
4232
+ }
4233
+ outputJson() {
4234
+ return this.outputStyle == "json";
4235
+ }
4236
+ outputPlain() {
4237
+ return this.outputStyle == "plain";
4238
+ }
4239
+ querySelectedInventory(tags = []) {
4240
+ return this.selectInventory(this.selectedInventory(), new Set(tags));
4241
+ }
4242
+ selectedInventory() {
4243
+ return this.queryInventory(this.selectedTags);
4244
+ }
4245
+ // returns hosts that have *all* of the given tags
4246
+ // each tag is a string of the form:
4247
+ // - foo
4248
+ // - bar+baz
4249
+ // the tags are interpreted to be in disjunctive-normal-form: a disjunction of conjunctions
4250
+ queryInventory(tags = /* @__PURE__ */ new Set()) {
4251
+ if (!this._config) {
4252
+ this.warn("Warning: Configuration not loaded, cannot query inventory.");
4253
+ return [];
4254
+ }
4255
+ const allHosts = this._config.hosts();
4256
+ if (tags.size === 0) {
4257
+ return allHosts;
4258
+ }
4259
+ return this.selectInventory(allHosts, new Set(tags));
4260
+ }
4261
+ selectInventory(hosts, tags = /* @__PURE__ */ new Set()) {
4262
+ if (S(tags).isEmpty()) {
4263
+ return hosts;
4264
+ }
4265
+ return A2(hosts).select((host) => host.hasAnyTag(tags));
4266
+ }
4267
+ // entrypoint for the cli: AGE_IDS=~/.secrets/age/test-jill.priv tsx bin/hostctl.ts inventory -t ubuntu
4268
+ printInventoryReport() {
4269
+ const configFromGetter = this.config;
4270
+ const config = configFromGetter;
4271
+ const allHosts = config.hosts();
4272
+ const allSecrets = config.secrets();
4273
+ const allIds = config.ids();
4274
+ if (this.outputPlain()) {
4275
+ let output = "Config file: " + this.configRef + "\n\nHosts:\n";
4276
+ if (allHosts.length > 0) {
4277
+ allHosts.forEach((host) => {
4278
+ output += ` - ${host.uri}:
4279
+ `;
4280
+ output += ` alias: ${host.alias || ""}
4281
+ `;
4282
+ output += ` user: ${host.user || ""}
4283
+ `;
4284
+ output += ` port: ${host.port || ""}
4285
+ `;
4286
+ output += ` tags: ${host.tags.join(", ")}
4287
+ `;
4288
+ });
4289
+ } else {
4290
+ output += " {}\n";
4291
+ }
4292
+ output += "\nSecrets:\n";
4293
+ if (allSecrets.size > 0) {
4294
+ allSecrets.forEach((secret, name) => {
4295
+ output += ` - ${name}:
4296
+ `;
4297
+ output += ` encrypted: ${secret.value.isEncrypted()}
4298
+ `;
4299
+ });
4300
+ } else {
4301
+ output += " {}\n";
4302
+ }
4303
+ output += "\nIdentities (Public Keys):\n";
4304
+ if (allIds.size > 0) {
4305
+ allIds.forEach((idGroup, name) => {
4306
+ output += ` - ${name}:
4307
+ `;
4308
+ output += ` publicKeys: ${idGroup.publicKeys.join(", ")}
4309
+ `;
4310
+ });
4311
+ } else {
4312
+ output += " {}\n";
4313
+ }
4314
+ console.log(output.trim());
4315
+ } else if (this.outputJson()) {
4316
+ const jsonData = {
4317
+ hosts: allHosts.map((h) => h.toObject()),
4318
+ secrets: {},
4319
+ identities: {}
4320
+ };
4321
+ allSecrets.forEach((secret, name) => {
4322
+ jsonData.secrets[name] = {
4323
+ name: secret.name,
4324
+ encrypted: secret.value.isEncrypted()
4325
+ // Consider if ids/recipients should be exposed here
4326
+ };
4327
+ });
4328
+ allIds.forEach((idGroup, name) => {
4329
+ jsonData.identities[name] = {
4330
+ name: idGroup.name || name,
4331
+ // Assuming NamedRecipientGroup has a name property
4332
+ publicKeys: idGroup.publicKeys,
4333
+ idRefs: idGroup.idRefs
4334
+ };
4335
+ });
4336
+ console.log(JSON.stringify(jsonData, null, 2));
4337
+ }
4338
+ }
4339
+ // entrypoint for the cli: AGE_IDS=~/.secrets/age/test-david.priv tsx bin/hostctl.ts inventory encrypt
4340
+ async encryptInventoryFile() {
4341
+ if (!this.configRef) {
4342
+ throw new Error("Cannot encrypt inventory: Configuration file reference is not set.");
4343
+ }
4344
+ const currentConfigRef = this.configRef;
4345
+ this.warn(`Encrypting inventory file: ${currentConfigRef}`);
4346
+ const configFile = new ConfigFile2(currentConfigRef);
4347
+ await configFile.load();
4348
+ await configFile.encryptAll();
4349
+ await configFile.save(currentConfigRef);
4350
+ }
4351
+ // entrypoint for the cli: AGE_IDS=~/.secrets/age/david.priv tsx bin/hostctl.ts inventory decrypt
4352
+ async decryptInventoryFile() {
4353
+ if (!this.configRef) {
4354
+ throw new Error("Cannot decrypt inventory: Configuration file reference is not set.");
4355
+ }
4356
+ const currentConfigRef = this.configRef;
4357
+ const configFile = new ConfigFile2(currentConfigRef);
4358
+ await configFile.load();
4359
+ let hasEncryptedSecrets = false;
4360
+ for (const secret of configFile._secrets.values()) {
4361
+ if (secret.value.isEncrypted()) {
4362
+ hasEncryptedSecrets = true;
4363
+ break;
4364
+ }
4365
+ }
4366
+ if (!hasEncryptedSecrets) {
4367
+ this.info("No encrypted secrets found to decrypt. Inventory is already decrypted.");
4368
+ return;
4369
+ }
4370
+ this.warn(`Decrypting inventory file: ${currentConfigRef}`);
4371
+ try {
4372
+ await configFile.decryptAllIfPossible();
4373
+ const successfullyUsedIdentityPaths = configFile.loadPrivateKeys().filter((identity2) => {
4374
+ try {
4375
+ return fs6.existsSync(identity2.identityFilePath);
4376
+ } catch (e) {
4377
+ return false;
4378
+ }
4379
+ }).map((i) => i.identityFilePath).join("\n");
4380
+ if (this.verbosity >= Verbosity.INFO && successfullyUsedIdentityPaths.length > 0) {
4381
+ this.info(`Decrypted with one or more of the following private keys:
4382
+ ${successfullyUsedIdentityPaths}`);
4383
+ } else if (this.verbosity >= Verbosity.INFO && hasEncryptedSecrets && successfullyUsedIdentityPaths.length === 0) {
4384
+ this.warn("Encrypted secrets found, but no specified AGE identities were successfully used or found.");
4385
+ }
4386
+ await configFile.save(currentConfigRef);
4387
+ } catch (error) {
4388
+ if (error.message?.includes("Unable to read identity file")) {
4389
+ throw new Error("Decryption failed: no identity matched or failed to decrypt due to key issue.");
4390
+ }
4391
+ if (error.message?.includes("No identity matched secret")) {
4392
+ throw new Error("Decryption failed: no identity matched the encrypted secrets.");
4393
+ }
4394
+ throw error;
4395
+ }
4396
+ }
4397
+ // entrypoint for the cli: AGE_IDS=~/.secrets/age/david.priv tsx bin/hostctl.ts -t ubuntu -- script.ts
4398
+ // e.g. AGE_IDS=~/.secrets/age/david.priv tsx bin/hostctl.ts -c david.yaml -t synology blog 4gb web1 -- script.ts
4399
+ async runScript(scriptRef, params) {
4400
+ const selectedHosts = this.selectedInventory();
4401
+ this.info(`Selected hosts: ${selectedHosts.length}`);
4402
+ const absoluteScriptRef = Path.new(scriptRef).absolute();
4403
+ this.debug(`absoluteScriptRef=${absoluteScriptRef}`);
4404
+ this.debug(`params=${util2.inspect(params)}`);
4405
+ const mod = await this.loadScriptAsModule(absoluteScriptRef.toString());
4406
+ this.debug(`mod=${util2.inspect(mod)}`);
4407
+ const packageFileDir = this.pathOfPackageJsonFile(absoluteScriptRef.toString());
4408
+ if (!packageFileDir) {
4409
+ console.error(
4410
+ chalk4.red(`Bundle failure. "${absoluteScriptRef}" nor any ancestor directory contains a package.json file.`)
4411
+ );
4412
+ return;
4413
+ }
4414
+ const taskFn = mod.default;
4415
+ const interactionHandler = new InteractionHandler();
4416
+ const localRuntime = new LocalRuntime(this, absoluteScriptRef, interactionHandler);
4417
+ if (this.outputPlain()) {
4418
+ this.info(`run: ${chalk4.yellow(absoluteScriptRef.toString())} ${chalk4.cyan(util2.inspect(params))}`);
4419
+ }
4420
+ let invocation;
4421
+ let scriptResult;
4422
+ try {
4423
+ invocation = await localRuntime.invokeRootTask(taskFn, params, `Running ${scriptRef}`);
4424
+ if (!invocation) {
4425
+ this.error(`Failed to invoke task for script: ${scriptRef}`);
4426
+ scriptResult = new Error(`Failed to invoke task for script: ${scriptRef}`);
4427
+ } else {
4428
+ scriptResult = await invocation.result;
4429
+ this.reportScriptResult(invocation, scriptResult);
4430
+ }
4431
+ } catch (e) {
4432
+ scriptResult = e;
4433
+ if (this.outputPlain()) {
4434
+ this.error(`Error running script ${scriptRef}: ${scriptResult.message}`);
4435
+ if (this.verbosity >= Verbosity.DEBUG && scriptResult.stack) {
4436
+ this.error(scriptResult.stack);
4437
+ }
4438
+ }
4439
+ if (invocation) {
4440
+ this.reportScriptResult(invocation, scriptResult);
4441
+ }
4442
+ }
4443
+ if (scriptResult instanceof Error) {
4444
+ process3.exit(1);
4445
+ }
4446
+ }
4447
+ // entrypoint for the cli: AGE_IDS=~/.secrets/age/david.priv tsx bin/hostctl.ts -t ubuntu -- script.ts
4448
+ // e.g. AGE_IDS=~/.secrets/age/david.priv tsx bin/hostctl.ts -c david.yaml -t synology blog 4gb web1 -- script.ts
4449
+ async runScriptRemote(scriptRef, params) {
4450
+ const selectedHosts = this.selectedInventory();
4451
+ this.info(`Selected hosts: ${selectedHosts.length}`);
4452
+ const absoluteScriptRef = Path.new(scriptRef).absolute();
4453
+ this.debug(`absoluteScriptRef=${absoluteScriptRef}`);
4454
+ this.debug(`params=${util2.inspect(params)}`);
4455
+ const mod = await this.loadScriptAsModule(absoluteScriptRef.toString());
4456
+ this.debug(`mod=${util2.inspect(mod)}`);
4457
+ const packageFileDir = this.pathOfPackageJsonFile(absoluteScriptRef.toString());
4458
+ if (!packageFileDir) {
4459
+ console.error(
4460
+ chalk4.red(`Bundle failure. "${absoluteScriptRef}" nor any ancestor directory contains a package.json file.`)
4461
+ );
4462
+ return;
4463
+ }
4464
+ const taskFn = mod.default;
4465
+ const interactionHandler = new InteractionHandler();
4466
+ const localRuntime = new LocalRuntime(this, absoluteScriptRef, interactionHandler);
4467
+ this.info(`run: ${chalk4.yellow(absoluteScriptRef.toString())} ${chalk4.cyan(util2.inspect(params))}`);
4468
+ let invocation;
4469
+ let scriptResult;
4470
+ try {
4471
+ const runAllRemoteParams = { taskFn, params };
4472
+ invocation = await localRuntime.invokeRootTask(
4473
+ runAllRemote_default,
4474
+ runAllRemoteParams,
4475
+ `Running ${scriptRef} on selected hosts`
4476
+ );
4477
+ if (!invocation) {
4478
+ this.error(`Failed to invoke task for script: ${scriptRef}`);
4479
+ scriptResult = new Error(`Failed to invoke task for script: ${scriptRef}`);
4480
+ } else {
4481
+ scriptResult = await invocation.result;
4482
+ this.reportScriptResult(invocation, scriptResult);
4483
+ }
4484
+ } catch (e) {
4485
+ scriptResult = e;
4486
+ if (this.outputPlain()) {
4487
+ this.error(`Error running script ${scriptRef}: ${scriptResult.message}`);
4488
+ if (this.verbosity >= Verbosity.DEBUG && scriptResult.stack) {
4489
+ this.error(scriptResult.stack);
4490
+ }
4491
+ }
4492
+ if (invocation) {
4493
+ this.reportScriptResult(invocation, scriptResult);
4494
+ }
4495
+ }
4496
+ if (scriptResult instanceof Error) {
4497
+ process3.exit(1);
4498
+ }
4499
+ }
4500
+ async walkInvocationTreePreorder(invocation, visitFn, visited = /* @__PURE__ */ new Set(), depth = 0) {
4501
+ if (visited.has(invocation)) return;
4502
+ visited.add(invocation);
4503
+ await visitFn(invocation, depth);
4504
+ for (const childInvocation of invocation.getChildren()) {
4505
+ await this.walkInvocationTreePreorder(childInvocation, visitFn, visited, depth + 1);
4506
+ }
4507
+ }
4508
+ async reportScriptResult(invocation, scriptResult) {
4509
+ if (this.outputPlain()) {
4510
+ this.warn("=== Evaluation ===");
4511
+ await this.walkInvocationTreePreorder(invocation, async (invocation2, depth) => {
4512
+ const indent = " ".repeat(depth);
4513
+ if (invocation2.getDescription() || invocation2.id) {
4514
+ const err = await invocation2.resultError();
4515
+ if (err) {
4516
+ if (this.verbosity >= Verbosity.WARN) {
4517
+ this.warn(
4518
+ `${indent}${chalk4.red(XMARK)} ${invocation2.getDescription() || invocation2.id} ${chalk4.red(util2.inspect(err))}`
4519
+ );
4520
+ if (this.verbosity >= Verbosity.INFO) {
4521
+ this.info(`${indent} ${chalk4.red(err.message)}`);
4522
+ if (this.verbosity >= Verbosity.DEBUG) {
4523
+ invocation2.stdout && this.debug(`${indent} ${chalk4.bgGreen(invocation2.stdout.trimEnd())}`);
4524
+ invocation2.stderr && this.debug(`${indent} ${chalk4.bgRed(invocation2.stderr.trimEnd())}`);
4525
+ }
4526
+ }
4527
+ }
4528
+ } else {
4529
+ if (this.verbosity >= Verbosity.WARN) {
4530
+ this.warn(`${indent}${chalk4.green(CHECKMARK)} ${invocation2.getDescription() || invocation2.id}`);
4531
+ if (this.verbosity >= Verbosity.INFO) {
4532
+ this.info(`${indent} ${chalk4.green(util2.inspect(await invocation2.getResultPromise()))}`);
4533
+ if (this.verbosity >= Verbosity.DEBUG) {
4534
+ invocation2.stdout && this.debug(`${indent} ${chalk4.bgGreen(invocation2.stdout.trimEnd())}`);
4535
+ invocation2.stderr && this.debug(`${indent} ${chalk4.bgRed(invocation2.stderr.trimEnd())}`);
4536
+ }
4537
+ }
4538
+ }
4539
+ }
4540
+ }
4541
+ });
4542
+ this.warn("=== Result ===");
4543
+ if (isError2(scriptResult)) {
4544
+ this.warn(chalk4.red(`${invocation.getDescription() || invocation.id} failed:`));
4545
+ this.warn(chalk4.red(scriptResult.message));
4546
+ if (scriptResult.stack) {
4547
+ this.warn(chalk4.red(scriptResult.stack));
4548
+ }
4549
+ } else if (scriptResult && typeof scriptResult === "object" && "exitCode" in scriptResult && typeof scriptResult.exitCode === "number") {
4550
+ const exitCode = scriptResult.exitCode;
4551
+ const message = scriptResult.message || "";
4552
+ this.warn(
4553
+ chalk4.yellow(
4554
+ `${invocation.getDescription() || invocation.id} requested exit with code ${exitCode}: ${message}`
4555
+ )
4556
+ );
4557
+ } else if (scriptResult === void 0) {
4558
+ this.warn(chalk4.green(`${invocation.getDescription() || invocation.id} completed successfully.`));
4559
+ } else {
4560
+ this.warn(
4561
+ chalk4.green(
4562
+ `${invocation.getDescription() || invocation.id} completed with result: ${util2.inspect(scriptResult, { depth: 3 })}`
4563
+ )
4564
+ );
4565
+ }
4566
+ } else if (this.outputJson()) {
4567
+ if (!(scriptResult instanceof Error)) {
4568
+ console.log(JSON.stringify(scriptResult, null, 2));
4569
+ } else {
4570
+ const errorJson = {
4571
+ error: scriptResult.message,
4572
+ stack: scriptResult.stack
4573
+ };
4574
+ console.log(JSON.stringify(errorJson, null, 2));
4575
+ }
4576
+ }
4577
+ }
4578
+ async loadScriptAsModule(scriptRef) {
4579
+ const mod = await import(scriptRef);
4580
+ return mod;
4581
+ }
4582
+ parseParams(scriptArgs) {
4583
+ return ParamMap.parse(scriptArgs).toObject();
4584
+ }
4585
+ // this function creates a bundle of a given directory
4586
+ async bundleProject(entrypointPath) {
4587
+ const nodeRuntime = new NodeRuntime(this.tmpDir);
4588
+ const entrypointDir = this.pathOfPackageJsonFile(entrypointPath);
4589
+ if (!entrypointDir) {
4590
+ console.error(
4591
+ chalk4.red(`Bundle failure. "${entrypointPath}" nor any ancestor directory contains a package.json file.`)
4592
+ );
4593
+ return;
4594
+ }
4595
+ const tasks = new Listr([], {
4596
+ exitOnError: false,
4597
+ concurrent: true
4598
+ });
4599
+ tasks.add([
4600
+ {
4601
+ title: `Bundling ${entrypointDir.toString()}`,
4602
+ task: async (ctx) => {
4603
+ await this.generateBundle(nodeRuntime, entrypointDir);
4604
+ }
4605
+ }
4606
+ ]);
4607
+ try {
4608
+ await tasks.run();
4609
+ } catch (e) {
4610
+ console.error(e);
4611
+ }
4612
+ }
4613
+ async generateBundle(nodeRuntime, packageFileDir) {
4614
+ await nodeRuntime.installIfNeeded();
4615
+ const result = await nodeRuntime.npmInstall(true, packageFileDir.toString());
4616
+ if (result.failure) throw new Error(result.err);
4617
+ const absoluteDirPath = packageFileDir.toString();
4618
+ const dirNameHash = sha256(absoluteDirPath).slice(0, 10);
4619
+ const bundleZipFile = this.tmpDir.join(`bundle${dirNameHash}.zip`);
4620
+ const zipPath = await zipDirectory(packageFileDir.toString(), bundleZipFile.toString());
4621
+ return Path.new(zipPath);
4622
+ }
4623
+ // walks the directory tree that contains the given path from leaf to root searching for the deepest directory
4624
+ // containing a package.json file and returns the absolute path of that directory
4625
+ pathOfPackageJsonFile(path3) {
4626
+ let p = Path.new(path3);
4627
+ while (true) {
4628
+ if (p.dirContains("package.json")) {
4629
+ return p.absolute();
4630
+ }
4631
+ if (p.isRoot()) {
4632
+ return null;
4633
+ }
4634
+ p = p.parent();
4635
+ }
4636
+ }
4637
+ async printRuntimeReport() {
4638
+ const nodeRuntime = new NodeRuntime(this.tmpDir);
4639
+ const reportObj = {
4640
+ nodePath: await nodeRuntime.nodePath(),
4641
+ npmPath: await nodeRuntime.npmPath()
4642
+ // summary: report?.getReport(),
4643
+ };
4644
+ this.info(reportObj);
4645
+ }
4646
+ async promptPassword(message = "Enter your password") {
4647
+ return await promptPassword({ message });
4648
+ }
4649
+ async installRuntime() {
4650
+ this.info(`creating node runtime with ${this.tmpDir.toString()}`);
4651
+ const nodeRuntime = new NodeRuntime(this.tmpDir);
4652
+ await nodeRuntime.installIfNeeded();
4653
+ }
4654
+ async bootstrap() {
4655
+ }
4656
+ };
4657
+
4658
+ // src/cli.ts
4659
+ import JSON5 from "json5";
4660
+
4661
+ // src/cli/run-resolver.ts
4662
+ import { simpleGit } from "simple-git";
4663
+ import filenamify from "filenamify";
4664
+ import { match as match6 } from "ts-pattern";
4665
+ import { dirname } from "path";
4666
+ import { fileURLToPath } from "url";
4667
+ async function resolveScriptPath(app, packageOrBundle, scriptRef, scriptArgs) {
4668
+ if (packageOrBundle && packageOrBundle.startsWith("core.") || !packageOrBundle && scriptRef && scriptRef.startsWith("core.")) {
4669
+ const name = packageOrBundle?.startsWith("core.") ? packageOrBundle : scriptRef;
4670
+ const relPath = name.replace(/^core\./, "").split(".").join(Path.sep());
4671
+ const moduleDir = Path.new(dirname(fileURLToPath(import.meta.url)));
4672
+ const coreRoot = moduleDir.parent().join("core");
4673
+ const candidate = coreRoot.join(`${relPath}.ts`);
4674
+ if (!candidate.isFile()) {
4675
+ throw new Error(`Core task script not found: ${candidate}`);
4676
+ }
4677
+ if (packageOrBundle?.startsWith("core.") && scriptRef) {
4678
+ scriptArgs = [scriptRef, ...scriptArgs];
4679
+ }
4680
+ return { scriptRef: candidate.toString(), scriptArgs };
4681
+ }
4682
+ if (!packageOrBundle) {
4683
+ throw new Error("No bundle or package specified.");
4684
+ }
4685
+ const currentPackageOrBundle = packageOrBundle;
4686
+ let resolvedScriptRef = scriptRef ?? "";
4687
+ await match6(currentPackageOrBundle).when(
4688
+ (path3) => Path.new(path3).isFile() && Path.new(path3).ext() === ".zip",
4689
+ async () => {
4690
+ const tmpName = filenamify(currentPackageOrBundle);
4691
+ const destPath = app.tmpDir.join(tmpName);
4692
+ await unzipDirectory(currentPackageOrBundle, destPath.toString());
4693
+ resolvedScriptRef = destPath.resolve(resolvedScriptRef).toString();
4694
+ }
4695
+ ).when(
4696
+ (path3) => Path.new(path3).isDirectory(),
4697
+ async () => {
4698
+ const tmpName = filenamify(currentPackageOrBundle);
4699
+ const destPath = app.tmpDir.join(tmpName);
4700
+ const pkgPath = Path.new(currentPackageOrBundle);
4701
+ await pkgPath.copy(destPath.toString());
4702
+ resolvedScriptRef = destPath.resolve(resolvedScriptRef).toString();
4703
+ }
4704
+ ).when(isValidUrl, async () => {
4705
+ const tmpName = filenamify(currentPackageOrBundle);
4706
+ const destPath = app.tmpDir.join(tmpName);
4707
+ const git = simpleGit();
4708
+ await git.clone(currentPackageOrBundle, destPath.toString());
4709
+ resolvedScriptRef = destPath.resolve(resolvedScriptRef).toString();
4710
+ }).otherwise(async () => {
4711
+ if (resolvedScriptRef) {
4712
+ A2(scriptArgs).prepend(resolvedScriptRef);
4713
+ }
4714
+ resolvedScriptRef = currentPackageOrBundle;
4715
+ });
4716
+ return { scriptRef: resolvedScriptRef, scriptArgs };
4717
+ }
4718
+ function isValidUrl(url) {
4719
+ try {
4720
+ new URL(url);
4721
+ return true;
4722
+ } catch {
4723
+ return false;
4724
+ }
4725
+ }
4726
+
4727
+ // src/cli.ts
4728
+ var isError2 = (value) => !!value && typeof value === "object" && "message" in value && typeof value.message === "string" && "stack" in value && typeof value.stack === "string";
4729
+ var logError = (message, error) => {
4730
+ console.error(`Error: ${message}`);
4731
+ if (isError2(error)) {
4732
+ console.error(error.stack);
4733
+ } else {
4734
+ try {
4735
+ const unknownError = new Error(
4736
+ `Unexpected value thrown: ${typeof error === "object" ? JSON.stringify(error) : String(error)}`
4737
+ );
4738
+ console.error(unknownError.stack);
4739
+ } catch {
4740
+ console.error(new Error(`Unexpected value thrown: non-stringifiable object`).stack);
4741
+ }
4742
+ }
4743
+ };
4744
+ function isValidUrl2(url) {
4745
+ try {
4746
+ new URL(url);
4747
+ return true;
4748
+ } catch (_) {
4749
+ return false;
4750
+ }
4751
+ }
4752
+ function increaseVerbosity(dummyValue, previous) {
4753
+ return previous + 1;
4754
+ }
4755
+ function decreaseVerbosity(dummyValue, previous) {
4756
+ return previous - 1;
4757
+ }
4758
+ var Cli = class {
4759
+ app;
4760
+ program;
4761
+ constructor() {
4762
+ this.app = new App();
4763
+ this.program = new cmdr.Command();
4764
+ this.program.configureHelp({ showGlobalOptions: true }).name("hostctl").version(version).option("-q, --quiet", "quiet; be less verbose; may be specified multiple times", decreaseVerbosity, 0).option(
4765
+ "-v, --verbose",
4766
+ "verbose; may be specified multiple times for increased verbosity",
4767
+ increaseVerbosity,
4768
+ Verbosity.WARN
4769
+ ).option("-c, --config <file path>", "config file path or http endpoint").option("--json", "output should be json formatted").option("-p, --password", "should prompt for sudo password?", false).option("-t, --tag <tags...>", "specify a tag (repeat for multiple tags)");
4770
+ this.program.command("bundle").alias("b").argument("[path]", `the path to bundle (e.g. hostctl bundle .)`, ".").description("bundle the given path as an npm project").action(this.handleBundle.bind(this));
4771
+ this.program.command("exec").alias("e").argument(
4772
+ "<command...>",
4773
+ `the command string to run, with optional arguments (e.g. hostctl exec sudo sh -c 'echo "$(whoami)"')`
4774
+ ).description("execute a command on the selected hosts (default)").action(this.handleExec.bind(this));
4775
+ const inventoryCmd = this.program.command("inventory").alias("i").description("manage inventory and view reports");
4776
+ inventoryCmd.command("report", { isDefault: true }).description("print out an inventory report (default)").action(this.handleInventory.bind(this));
4777
+ inventoryCmd.command("encrypt").alias("e").description("encrypt the inventory file").action(this.handleInventoryEncrypt.bind(this));
4778
+ inventoryCmd.command("decrypt").alias("d").description("decrypt the inventory file").action(this.handleInventoryDecrypt.bind(this));
4779
+ inventoryCmd.command("list").alias("ls").description("list the hosts in the inventory file").action(this.handleInventoryList.bind(this));
4780
+ this.program.command("run").alias("r").argument(
4781
+ "[package_or_bundle]",
4782
+ `the package or bundle to run the specified <script> from.
4783
+ The package or bundle may be either:
4784
+ - a directory in the local filesystem, e.g. /my/package/foo
4785
+ - a git repository, e.g. https://github.com/hostctl/core
4786
+ - a zipped package (a bundle), e.g. packagebundle123.zip
4787
+ `
4788
+ ).argument(
4789
+ "[script]",
4790
+ `the hostctl script to run (e.g. hostctl run myscript OR hostctl run mypackage myscript).
4791
+ The script may be specified as either:
4792
+ - a package followed by the script, e.g. hostctl run github.com/hostctl/core echo args:hello,world
4793
+ - a script, e.g. hostctl run main.ts
4794
+ - a zipped package (a bundle), e.g. hostctl run packagebundle.zip echo args:hello,world`
4795
+ ).argument("[script arguments...]", "the runtime arguments to the script.").description("run a hostctl script (default)").option(
4796
+ "-f, --file <path>",
4797
+ "runtime parameters should be read from file at <path> containing json object representing params to supply to the script"
4798
+ ).option(
4799
+ '-p, --params "<json object>"',
4800
+ "runtime parameters supplied as a string to be parsed as a json object representing params to supply to the script"
4801
+ ).option("-r, --remote", "run the script on remote hosts specified by tags via SSH orchestration").action(this.handleRun.bind(this));
4802
+ const runtimeCmd = this.program.command("runtime").alias("rt").description("print out a report of the runtime environment").action(this.handleRuntime.bind(this));
4803
+ runtimeCmd.command("install").alias("i").description("install a temporary runtime environment on the local host if needed").action(this.handleRuntimeInstall.bind(this));
4804
+ }
4805
+ async handleBundle(path3, options, cmd) {
4806
+ const opts = cmd.optsWithGlobals();
4807
+ await this.loadApp(opts);
4808
+ this.app.bundleProject(path3);
4809
+ }
4810
+ async handleInventory(options, cmd) {
4811
+ const opts = cmd.optsWithGlobals();
4812
+ await this.loadApp(opts);
4813
+ this.app.printInventoryReport();
4814
+ }
4815
+ async handleInventoryEncrypt(options, cmd) {
4816
+ const opts = cmd.optsWithGlobals();
4817
+ await this.loadApp(opts);
4818
+ await this.app.encryptInventoryFile();
4819
+ }
4820
+ async handleInventoryDecrypt(options, cmd) {
4821
+ const opts = cmd.optsWithGlobals();
4822
+ await this.loadApp(opts);
4823
+ await this.app.decryptInventoryFile();
4824
+ }
4825
+ async handleInventoryList(options, cmd) {
4826
+ const opts = cmd.optsWithGlobals();
4827
+ await this.loadApp(opts);
4828
+ if (!this.app.config) {
4829
+ console.log("Configuration could not be loaded. Cannot list hosts.");
4830
+ if (!this.app.configRef) {
4831
+ console.log("(Also, no configuration file path was found by the app instance)");
4832
+ }
4833
+ return;
4834
+ }
4835
+ console.log("Config file: " + this.app.configRef);
4836
+ const hosts = this.app.selectedInventory();
4837
+ if (hosts.length > 0) {
4838
+ console.log("\nHosts in inventory:");
4839
+ hosts.forEach((host) => {
4840
+ let hostIdentifier = host.shortName || "(Unnamed host)";
4841
+ console.log(` - ${hostIdentifier}`);
4842
+ if (host.tags && host.tags.length > 0) {
4843
+ console.log(` Tags: ${host.tags.join(", ")}`);
4844
+ }
4845
+ });
4846
+ } else {
4847
+ const selectedTagsSet = this.app.selectedTags;
4848
+ const selectedTagsArray = Array.from(selectedTagsSet);
4849
+ if (selectedTagsArray.length > 0) {
4850
+ console.log(`No hosts found matching tags: ${selectedTagsArray.join(", ")}`);
4851
+ } else {
4852
+ console.log("No hosts found in the inventory.");
4853
+ }
4854
+ }
4855
+ }
4856
+ async handleExec(commandParts, options, cmd) {
4857
+ const opts = cmd.optsWithGlobals();
4858
+ await this.loadApp(opts);
4859
+ this.app.warn(`Executing command: ${commandParts}`);
4860
+ const success = await this.app.execute(commandParts);
4861
+ if (!success) {
4862
+ process4.exitCode = 1;
4863
+ }
4864
+ }
4865
+ async handleRun(packageOrBundle, scriptRef, scriptArgs, options, cmd) {
4866
+ const opts = cmd.optsWithGlobals();
4867
+ await this.loadApp(opts);
4868
+ this.app.debug(`tags: ${opts.tag}, remote: ${opts.remote}`);
4869
+ const resolved = await resolveScriptPath(this.app, packageOrBundle, scriptRef, scriptArgs);
4870
+ scriptRef = resolved.scriptRef;
4871
+ scriptArgs = resolved.scriptArgs;
4872
+ let params = {};
4873
+ try {
4874
+ if (options.params) {
4875
+ params = JSON5.parse(options.params);
4876
+ } else if (options.file) {
4877
+ const contents = File.readSync(options.file);
4878
+ params = JSON5.parse(contents);
4879
+ } else {
4880
+ params = this.app.parseParams(scriptArgs);
4881
+ }
4882
+ } catch (err) {
4883
+ this.app.error(
4884
+ `Failed to parse params as JSON5: ${err.message}`,
4885
+ `params: ${JSON5.stringify(options.params)}`
4886
+ );
4887
+ throw new Error(`Failed to parse params as JSON5: ${err.message}`);
4888
+ }
4889
+ if (!scriptRef) {
4890
+ this.program.help();
4891
+ }
4892
+ this.app.debug(`Resolved script: ${scriptRef}`);
4893
+ this.app.debug(`Script params: ${JSON.stringify(params)}`);
4894
+ if (opts.remote) {
4895
+ await this.app.runScriptRemote(scriptRef, params);
4896
+ } else {
4897
+ await this.app.runScript(scriptRef, params);
4898
+ }
4899
+ }
4900
+ deriveConfigRef(suppliedConfigRef) {
4901
+ if (suppliedConfigRef && Path.new(suppliedConfigRef).isFile()) {
4902
+ return Path.new(suppliedConfigRef).toString();
4903
+ }
4904
+ const foundPath = Path.cwd().selfAndAncestors().find((p) => p.join("hostctl.yaml").isFile());
4905
+ if (foundPath) {
4906
+ return foundPath.join("hostctl.yaml").toString();
4907
+ }
4908
+ const homeConfigPath = Path.homeDir().join(".hostctl", "hostctl.yaml");
4909
+ if (homeConfigPath.isFile()) {
4910
+ return homeConfigPath.toString();
4911
+ }
4912
+ if (suppliedConfigRef && isValidUrl2(suppliedConfigRef)) {
4913
+ return suppliedConfigRef;
4914
+ }
4915
+ throw new Error(
4916
+ "Could not find a hostctl configuration file. Searched for 'hostctl.yaml' in the current directory, ancestor directories, and '~/.hostctl/hostctl.yaml'. If you intended to use a URL or a specific file path, please ensure it's correct and accessible via the -c option."
4917
+ );
4918
+ }
4919
+ async loadApp(opts) {
4920
+ const derivedConfigRef = this.deriveConfigRef(opts.config);
4921
+ await this.app.loadConfig(derivedConfigRef);
4922
+ const verbosity = opts.quiet + opts.verbose;
4923
+ this.app.setVerbosity(verbosity);
4924
+ if (opts.json) {
4925
+ this.app.setOutputStyle("json");
4926
+ } else {
4927
+ this.app.setOutputStyle("plain");
4928
+ }
4929
+ let tagsToSet = [];
4930
+ if (typeof opts.tag === "string") {
4931
+ tagsToSet = [opts.tag];
4932
+ } else if (Array.isArray(opts.tag)) {
4933
+ tagsToSet = opts.tag;
4934
+ }
4935
+ this.app.setSelectedTags(tagsToSet);
4936
+ }
4937
+ async run() {
4938
+ try {
4939
+ await this.program.parseAsync(process4.argv);
4940
+ } catch (e) {
4941
+ logError("hostctl error:", e);
4942
+ }
4943
+ }
4944
+ async handleRuntime(options, cmd) {
4945
+ const opts = cmd.optsWithGlobals();
4946
+ await this.loadApp(opts);
4947
+ await this.app.printRuntimeReport();
4948
+ }
4949
+ async handleRuntimeInstall(options, cmd) {
4950
+ const opts = cmd.optsWithGlobals();
4951
+ await this.loadApp(opts);
4952
+ await this.app.installRuntime();
4953
+ }
4954
+ };
4955
+
4956
+ // src/bin/hostctl.ts
4957
+ var cli = new Cli();
4958
+ cli.program.parseAsync(process.argv).catch((error) => {
4959
+ console.error("Unhandled error in hostctl binary:", error);
4960
+ process.exit(1);
4961
+ });
4962
+ //# sourceMappingURL=hostctl.js.map