hostctl 0.1.37 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +201 -189
- package/dist/bin/hostctl.js +960 -250
- package/dist/bin/hostctl.js.map +1 -1
- package/dist/index.d.ts +154 -72
- package/dist/index.js +4776 -3184
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -202,7 +202,7 @@ johndoe: - age1jx3ec0y5hctkfwfut4p8cj08ulcezqex8r8slwlwgwy8k2plx5ksdzy8ll
|
|
|
202
202
|
janedoe: - age1qdqv054hmfmnxk56dkjm3cq8qz4lxfl7tvqfp0rqdl0nyygjlccqw0ly40
|
|
203
203
|
|
|
204
204
|
hostctl on main [!] is 📦 v0.1.31 via 🥟 v1.2.10 via v23.11.0
|
|
205
|
-
❯ AGE_IDS="example/sample-age-ids
|
|
205
|
+
❯ AGE_IDS="example/sample-age-ids/\*.priv" hostctl inventory -c example/hostctl.yaml decrypt -v
|
|
206
206
|
Decrypting inventory file: example/hostctl.yaml
|
|
207
207
|
Decrypted with one or more of the following private keys:
|
|
208
208
|
example/sample-age-ids/johndoe.priv
|
|
@@ -227,7 +227,7 @@ ids:
|
|
|
227
227
|
johndoe: - age1jx3ec0y5hctkfwfut4p8cj08ulcezqex8r8slwlwgwy8k2plx5ksdzy8ll
|
|
228
228
|
janedoe: - age1qdqv054hmfmnxk56dkjm3cq8qz4lxfl7tvqfp0rqdl0nyygjlccqw0ly40
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
````
|
|
231
231
|
|
|
232
232
|
## Vision
|
|
233
233
|
|
|
@@ -294,6 +294,24 @@ The vision for `hostctl` is to be an Ansible replacement that is significantly e
|
|
|
294
294
|
- **`core.pkg.remove`**: Removes a package.
|
|
295
295
|
- **`core.pkg.update`**: Updates a package.
|
|
296
296
|
|
|
297
|
+
#### Abstract Package Management (Cross-Platform, Non-Interactive)
|
|
298
|
+
- **`core.pkg.abstract-install`**: Install packages using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
299
|
+
- **`core.pkg.abstract-uninstall`**: Uninstall packages using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
300
|
+
- **`core.pkg.abstract-update`**: Update package sources using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
301
|
+
- **`core.pkg.abstract-upgrade`**: Upgrade packages using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
302
|
+
- **`core.pkg.abstract-search`**: Search for packages using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
303
|
+
- **`core.pkg.abstract-list`**: List installed packages using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
304
|
+
- **`core.pkg.abstract-clean`**: Clean package cache using auto-detected or specified package manager (non-interactive, no progress bars).
|
|
305
|
+
|
|
306
|
+
**Supported Package Managers**: Apt, Apk, Yum, Dnf, Pacman, Zypper, Eopkg, Winget, Choco, Brew
|
|
307
|
+
|
|
308
|
+
**Non-Interactive Features**: All abstract package management tasks run completely non-interactively with:
|
|
309
|
+
- No user prompts or confirmations
|
|
310
|
+
- No progress bars or interactive indicators
|
|
311
|
+
- No color output or terminal formatting
|
|
312
|
+
- Automatic confirmations via environment variables and command flags
|
|
313
|
+
- Clean, machine-readable output suitable for CI/CD pipelines
|
|
314
|
+
|
|
297
315
|
### Service and Process Management
|
|
298
316
|
- **`core.systemd.disable`**: Disables a systemd service.
|
|
299
317
|
- **`core.systemd.enable`**: Enables a systemd service.
|
|
@@ -437,6 +455,186 @@ hostctl bundle
|
|
|
437
455
|
hostctl bundle ./my-hostctl-project
|
|
438
456
|
```
|
|
439
457
|
|
|
458
|
+
#### `pkg`
|
|
459
|
+
|
|
460
|
+
Manages `hostctl` packages with a comprehensive package management system that maintains a manifest of installed packages and their tasks.
|
|
461
|
+
|
|
462
|
+
**Usage:**
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
hostctl pkg [subcommand]
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Subcommands:**
|
|
469
|
+
|
|
470
|
+
- **`create <package-name>`**
|
|
471
|
+
- Scaffolds a new `hostctl` package in the current directory.
|
|
472
|
+
- **Usage:** `hostctl pkg create my-new-task`
|
|
473
|
+
- **Options:**
|
|
474
|
+
- `--lang <language>`: Specify the language for the package (`typescript` or `javascript`). Defaults to `typescript`.
|
|
475
|
+
|
|
476
|
+
- **`install <package-source>`**
|
|
477
|
+
- Installs a `hostctl` package and maintains it in the package manifest.
|
|
478
|
+
- The `<package-source>` can be a local path, a Git URL, or an npm package name.
|
|
479
|
+
- **Usage:**
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
# Install from a local directory
|
|
483
|
+
hostctl pkg install ./my-local-task
|
|
484
|
+
|
|
485
|
+
# Install from a Git repository
|
|
486
|
+
hostctl pkg install https://github.com/user/my-hostctl-task.git
|
|
487
|
+
|
|
488
|
+
# Install from npm
|
|
489
|
+
hostctl pkg install my-npm-hostctl-task
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
- **`list`** (alias: `ls`)
|
|
493
|
+
- Lists all installed `hostctl` packages with their discovered tasks.
|
|
494
|
+
- **Usage:** `hostctl pkg list` or `hostctl pkg ls`
|
|
495
|
+
- **Output Example:**
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
Found 1 package(s):
|
|
499
|
+
|
|
500
|
+
• hostsfile (v1.0.0) - hostsfile package [https_github.com_davidkellis_hostsfile]
|
|
501
|
+
└─ add_entry
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
- **`remove <package-identifier>`** (alias: `rm`)
|
|
505
|
+
- Removes an installed `hostctl` package and updates the manifest.
|
|
506
|
+
- The `<package-identifier>` can be either the package name or the directory name.
|
|
507
|
+
- **Usage:** `hostctl pkg remove my-package` or `hostctl pkg rm my-package`
|
|
508
|
+
|
|
509
|
+
## Package Management System
|
|
510
|
+
|
|
511
|
+
`hostctl` includes a comprehensive package management system that allows you to install, manage, and run tasks from external packages. This system maintains a manifest of installed packages and provides intelligent task discovery and resolution.
|
|
512
|
+
|
|
513
|
+
### Package Manifest
|
|
514
|
+
|
|
515
|
+
The package management system maintains a `manifest.json` file in `~/.hostctl/packages/` that tracks all installed packages. This manifest contains:
|
|
516
|
+
|
|
517
|
+
- **Package Source Mapping**: Each package source (URL, Git repo, local path) is mapped to package information
|
|
518
|
+
- **Package Metadata**: Name, version, description, and installation directory
|
|
519
|
+
- **Task Discovery**: Automatically discovered tasks within each package
|
|
520
|
+
- **Persistent Storage**: Package information persists across sessions
|
|
521
|
+
|
|
522
|
+
### Task Discovery
|
|
523
|
+
|
|
524
|
+
When a package is installed, `hostctl` automatically discovers tasks within the package by scanning for:
|
|
525
|
+
|
|
526
|
+
- **TypeScript/JavaScript Files**: `.ts` and `.js` files in the package root
|
|
527
|
+
- **Subdirectories**: Tasks in `tasks/` and `src/` subdirectories
|
|
528
|
+
- **Default Tasks**: Common default task files like `index.ts`, `main.ts`, etc.
|
|
529
|
+
|
|
530
|
+
### Task Resolution
|
|
531
|
+
|
|
532
|
+
The `hostctl run` command now supports multiple resolution strategies:
|
|
533
|
+
|
|
534
|
+
1. **Built-in Core Tasks**: `hostctl run core.echo hello`
|
|
535
|
+
2. **Installed Package Tasks**:
|
|
536
|
+
- Default task: `hostctl run https://github.com/user/repo`
|
|
537
|
+
- Specific task: `hostctl run https://github.com/user/repo:taskname`
|
|
538
|
+
3. **Relative Paths**: `hostctl run ./script.ts`
|
|
539
|
+
|
|
540
|
+
### Package Resolution Examples
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
# Install a package
|
|
544
|
+
hostctl pkg install https://github.com/davidkellis/hostsfile
|
|
545
|
+
|
|
546
|
+
# List installed packages and their tasks
|
|
547
|
+
hostctl pkg list
|
|
548
|
+
|
|
549
|
+
# Run the default task from an installed package
|
|
550
|
+
hostctl run https://github.com/davidkellis/hostsfile names:foo,bar
|
|
551
|
+
|
|
552
|
+
# Run a specific task from an installed package
|
|
553
|
+
hostctl run https://github.com/davidkellis/hostsfile:add_entry names:foo,bar
|
|
554
|
+
|
|
555
|
+
# Run core tasks
|
|
556
|
+
hostctl run core.echo hello
|
|
557
|
+
|
|
558
|
+
# Run relative paths (still works)
|
|
559
|
+
hostctl run example/echo.ts hello
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Abstract Package Management Examples
|
|
563
|
+
|
|
564
|
+
The abstract package management system automatically detects your platform's package manager and provides a unified interface:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
# Auto-detect and update package sources
|
|
568
|
+
hostctl run core.pkg.abstractUpdate
|
|
569
|
+
|
|
570
|
+
# Search for packages (auto-detects package manager)
|
|
571
|
+
hostctl run core.pkg.abstractSearch query:nginx
|
|
572
|
+
|
|
573
|
+
# Install packages (auto-detects package manager)
|
|
574
|
+
hostctl run core.pkg.abstractInstall package:curl
|
|
575
|
+
|
|
576
|
+
# Install multiple packages
|
|
577
|
+
hostctl run core.pkg.abstractInstall package:curl,wget,git
|
|
578
|
+
|
|
579
|
+
# Force use of specific package manager
|
|
580
|
+
hostctl run core.pkg.abstractInstall package:nginx packageManager:apt
|
|
581
|
+
|
|
582
|
+
# List installed packages
|
|
583
|
+
hostctl run core.pkg.abstractList
|
|
584
|
+
|
|
585
|
+
# Upgrade all packages
|
|
586
|
+
hostctl run core.pkg.abstractUpgrade
|
|
587
|
+
|
|
588
|
+
# Clean package cache
|
|
589
|
+
hostctl run core.pkg.abstractClean
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**Supported Platforms**: Ubuntu/Debian (apt), Alpine (apk), CentOS/RHEL (yum/dnf), Arch Linux (pacman), openSUSE (zypper), Solus (eopkg), Windows (winget/choco), macOS (brew)
|
|
593
|
+
|
|
594
|
+
### Package Structure
|
|
595
|
+
|
|
596
|
+
A `hostctl` package should follow this structure:
|
|
597
|
+
|
|
598
|
+
```
|
|
599
|
+
my-hostctl-package/
|
|
600
|
+
├── package.json # Package metadata
|
|
601
|
+
├── index.ts # Default task (optional)
|
|
602
|
+
├── task1.ts # Individual task
|
|
603
|
+
├── task2.ts # Individual task
|
|
604
|
+
├── tasks/ # Task subdirectory (optional)
|
|
605
|
+
│ ├── task3.ts
|
|
606
|
+
│ └── task4.ts
|
|
607
|
+
└── src/ # Source subdirectory (optional)
|
|
608
|
+
├── task5.ts
|
|
609
|
+
└── task6.ts
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Package Dependencies
|
|
613
|
+
|
|
614
|
+
Each installed package maintains its own `node_modules` directory, ensuring that:
|
|
615
|
+
|
|
616
|
+
- Dependencies are isolated per package
|
|
617
|
+
- The `hostctl` runtime is available to each package
|
|
618
|
+
- Packages can have their own version requirements
|
|
619
|
+
|
|
620
|
+
### Package Sources
|
|
621
|
+
|
|
622
|
+
The package management system supports multiple source types:
|
|
623
|
+
|
|
624
|
+
- **Local Paths**: `./my-package` or `/path/to/package`
|
|
625
|
+
- **Git Repositories**: `https://github.com/user/repo.git` or `git@github.com:user/repo.git`
|
|
626
|
+
- **GitHub Shorthand**: `github.com/user/repo`
|
|
627
|
+
- **NPM Packages**: `@scope/package-name` or `package-name`
|
|
628
|
+
|
|
629
|
+
### Task Execution Context
|
|
630
|
+
|
|
631
|
+
When running tasks from installed packages:
|
|
632
|
+
|
|
633
|
+
- Tasks have access to the full `hostctl` runtime API
|
|
634
|
+
- Package dependencies are available in the task's `node_modules`
|
|
635
|
+
- Tasks can use all core tasks and utilities
|
|
636
|
+
- Remote execution works seamlessly with installed package tasks
|
|
637
|
+
|
|
440
638
|
#### `inventory` (alias: `i`)
|
|
441
639
|
|
|
442
640
|
Manages and displays information about the host inventory.
|
|
@@ -565,7 +763,6 @@ When working with the inventory decryption functionality, it's important to unde
|
|
|
565
763
|
1. **Hostname Storage**: In the YAML configuration, hostnames are stored as the keys in the `hosts` map, not as properties inside each host object. When a host is serialized to YAML via `Host.toYAML()`, the `hostname` field is not included because it's represented by the map key.
|
|
566
764
|
|
|
567
765
|
2. **Verbosity and Logging**: The inventory decryption process follows the general verbosity rules of hostctl:
|
|
568
|
-
|
|
569
766
|
- By default, only ERROR level messages are shown
|
|
570
767
|
- INFO level messages (like "No encrypted secrets found to decrypt. Inventory is already decrypted.") require the `-v` flag to be visible
|
|
571
768
|
- For idempotent operations like decrypting an already-decrypted inventory, you need to use the `-v` flag to see confirmation messages
|
|
@@ -705,7 +902,7 @@ The `TaskContext` is the heart of the scripting API. It's passed to your `run` f
|
|
|
705
902
|
- `ssh(hostSelector, taskDefinition, params?): Promise<TRemoteReturn[] | Error>`: Executes another `hostctl` task on one or more remote hosts.
|
|
706
903
|
- **`hostSelector`**: A string, an array of strings, a `Host` object, or an array of `Host` objects to specify the target hosts from your inventory.
|
|
707
904
|
- **`taskDefinition`**: The task to execute on the remote host(s).
|
|
708
|
-
-
|
|
905
|
+
- `params`: The parameters to pass to the remote task.
|
|
709
906
|
- `file: FileOperations`: An object for performing file operations on the target host.
|
|
710
907
|
- `read(path)`: Reads the content of a file.
|
|
711
908
|
- `write(path, content)`: Writes content to a file.
|
|
@@ -835,191 +1032,6 @@ async function run(context: TaskContext<InstallNginxParams>): Promise<void> {
|
|
|
835
1032
|
export default task(run, 'Install Nginx on a Debian-based system');
|
|
836
1033
|
```
|
|
837
1034
|
|
|
838
|
-
## Roadmap: Expanding Capabilities
|
|
839
|
-
|
|
840
|
-
To further enhance `hostctl` and position it as a comprehensive Ansible alternative, the following task primitives are planned for future development. These aim to provide built-in support for common system administration and automation workflows:
|
|
841
|
-
|
|
842
|
-
1. **File Management:**
|
|
843
|
-
- `file.replace` (regex substitution)
|
|
844
|
-
- `file.fetch` (remote → local)
|
|
845
|
-
|
|
846
|
-
2. **Package Management:**
|
|
847
|
-
- `pkg.purge`
|
|
848
|
-
- `pkg.autoremove`
|
|
849
|
-
|
|
850
|
-
3. **User & Group Management:**
|
|
851
|
-
- `user.password` (change password)
|
|
852
|
-
|
|
853
|
-
4. **System Operations:**
|
|
854
|
-
- `system.timezone`
|
|
855
|
-
- `system.mount`
|
|
856
|
-
|
|
857
|
-
5. **Archive Management:**
|
|
858
|
-
- `archive.compress`
|
|
859
|
-
- `archive.extract`
|
|
860
|
-
- `archive.unarchive` (auto-detect format)
|
|
861
|
-
|
|
862
|
-
6. **Networking & Firewall:**
|
|
863
|
-
- `firewall.status`
|
|
864
|
-
- `firewall.add_rule`
|
|
865
|
-
- `firewall.remove_rule`
|
|
866
|
-
- `network.interfaces`
|
|
867
|
-
- `network.get_facts`
|
|
868
|
-
|
|
869
|
-
7. **Templating & Control Flow:**
|
|
870
|
-
- Enhanced templating (diff mode, variables, idempotent)
|
|
871
|
-
- `retry`
|
|
872
|
-
- `until` (wait for condition)
|
|
873
|
-
- `include_tasks` (run another script)
|
|
874
|
-
- `wait_for`
|
|
875
|
-
- `assert`
|
|
876
|
-
- Improved looping & retry helpers
|
|
877
|
-
|
|
878
|
-
This roadmap will evolve, and contributions are welcome!
|
|
879
|
-
|
|
880
|
-
3. **Define ResultTypes** (optional but recommended): Specify the structure of the result your task will return (e.g., `MyTaskResult`).
|
|
881
|
-
4. **Implement the `run` function**: This is the core logic of your task.
|
|
882
|
-
5. **Export the task**: Use `export default task(runFunction, {description: "Optional description"});`.
|
|
883
|
-
|
|
884
|
-
Here's a template:
|
|
885
|
-
|
|
886
|
-
```typescript
|
|
887
|
-
// my-task.ts
|
|
888
|
-
import { task, type TaskContext, type IInvocation, type LogLevel } from 'hostctl'; // Adjust path if developing hostctl itself
|
|
889
|
-
// For users of the hostctl package, it would typically be:
|
|
890
|
-
// import { task, type TaskContext, type IInvocation, type LogLevel, Verbosity } from "hostctl";
|
|
891
|
-
|
|
892
|
-
// (If developing hostctl, Verbosity might be imported from a relative path like "../app")
|
|
893
|
-
import { Verbosity } from '../app'; // Example for internal development path
|
|
894
|
-
|
|
895
|
-
// 1. Define the structure of parameters your task expects
|
|
896
|
-
export type MyTaskParams = {
|
|
897
|
-
targetSystem: string;
|
|
898
|
-
enableFeature?: boolean;
|
|
899
|
-
};
|
|
900
|
-
|
|
901
|
-
// 2. Define the structure of the result your task will return
|
|
902
|
-
export interface MyTaskResult {
|
|
903
|
-
success: boolean;
|
|
904
|
-
details?: string;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// 3. Implement the core task logic in a 'run' function
|
|
908
|
-
async function run(
|
|
909
|
-
context: TaskContext<MyTaskParams>, // First argument is the TaskContext
|
|
910
|
-
): Promise<MyTaskResult> {
|
|
911
|
-
// Access parameters passed to the script via context.params
|
|
912
|
-
const { targetSystem, enableFeature = false } = context.params;
|
|
913
|
-
|
|
914
|
-
// Use 'this' for invocation-specific operations
|
|
915
|
-
this.log(Verbosity.INFO as LogLevel, `Configuring ${targetSystem}...`);
|
|
916
|
-
|
|
917
|
-
try {
|
|
918
|
-
// Use context for runtime utilities like 'exec' or 'file' operations
|
|
919
|
-
const { stdout } = await context.exec(
|
|
920
|
-
`configure-system --target ${targetSystem} ${enableFeature ? '--enable' : ''}`,
|
|
921
|
-
);
|
|
922
|
-
this.log(Verbosity.DEBUG as LogLevel, `Configuration output: ${stdout}`);
|
|
923
|
-
|
|
924
|
-
// Example of using this.sh (often a shorthand for context.exec for simple commands)
|
|
925
|
-
// const { stdout: diskSpace } = await this.sh("df -h /");
|
|
926
|
-
// this.log(Verbosity.INFO as LogLevel, `Disk space: ${diskSpace}`);
|
|
927
|
-
|
|
928
|
-
return { success: true, details: 'System configured successfully.' };
|
|
929
|
-
} catch (error: any) {
|
|
930
|
-
this.log(Verbosity.ERROR as LogLevel, `Error during configuration: ${error.message}`);
|
|
931
|
-
return { success: false, details: error.message };
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// 4. Export the task, optionally with a dynamic description
|
|
936
|
-
// The '{{ args?.join(" ") }}' uses Handlebars templating.
|
|
937
|
-
// 'args' needs to be available in the template context for this to work.
|
|
938
|
-
export default task(run, 'Configures {{ targetSystem }} with feature: {{ enableFeature }}');
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
### The `run` Function Signature
|
|
942
|
-
|
|
943
|
-
The `run` function is where your task's main logic resides. It must conform to the following signature:
|
|
944
|
-
|
|
945
|
-
`async function run(context: TaskContext<TParams>): Promise<TResult>`
|
|
946
|
-
|
|
947
|
-
- **`context: TaskContext<TParams>`**: This is the **first and only argument** passed to your `run` function. It's a crucial object providing access to:
|
|
948
|
-
|
|
949
|
-
- `context.params: TParams`: An object containing the parameters passed to your script from the command line (e.g., `args:key,value` or via `--params <json_string>`). `TParams` is a type interface you define to structure these expected parameters.
|
|
950
|
-
- `context.log`, `context.exec`: The same logging and command execution functions available via `this`.
|
|
951
|
-
- `context.file: FileSystemOperations`: An object with methods for file system operations (`read`, `write`, `exists`, `mkdir`, `rm`) that work on the target host (local or remote).
|
|
952
|
-
- `context.host?: Host`: If the task is executing on a remote host, this object provides details about that host.
|
|
953
|
-
- `context.cwd: string`: The current working directory of the task.
|
|
954
|
-
|
|
955
|
-
- **`Promise<TResult>`**: Your `run` function must be `async` and should return a `Promise`. The value this promise resolves to is considered the result of your task. `TResult` is a type interface you define for structuring this result.
|
|
956
|
-
|
|
957
|
-
### Defining Parameters (`TParams`) and Results (`TResult`)
|
|
958
|
-
|
|
959
|
-
It's highly recommended to define TypeScript interfaces or types for your task's parameters (`TParams`) and its return value (`TResult`). This greatly improves code clarity, maintainability, and enables type checking.
|
|
960
|
-
|
|
961
|
-
```typescript
|
|
962
|
-
// For parameters
|
|
963
|
-
export type MyScriptParams = {
|
|
964
|
-
inputFile: string;
|
|
965
|
-
outputDir?: string;
|
|
966
|
-
forceOverwrite: boolean;
|
|
967
|
-
};
|
|
968
|
-
|
|
969
|
-
// For results
|
|
970
|
-
export interface MyScriptResult {
|
|
971
|
-
filesProcessed: number;
|
|
972
|
-
errorsEncountered: number;
|
|
973
|
-
outputLocation?: string;
|
|
974
|
-
}
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
### Example: The `echo` Task (from `src/core/echo.ts`)
|
|
978
|
-
|
|
979
|
-
This task demonstrates the core concepts:
|
|
980
|
-
|
|
981
|
-
```typescript
|
|
982
|
-
import { task, type TaskContext, type IInvocation, type LogLevel } from 'hostctl';
|
|
983
|
-
import { Verbosity } from 'hostctl/app'; // Path for user-facing package
|
|
984
|
-
|
|
985
|
-
// 1. Define Parameters
|
|
986
|
-
export type EchoParams = {
|
|
987
|
-
args: string[]; // Expects an array of strings named 'args'
|
|
988
|
-
};
|
|
989
|
-
|
|
990
|
-
// 2. Define Result
|
|
991
|
-
export interface EchoResult {
|
|
992
|
-
stdout: string; // The echoed output
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// 3. Implement the 'run' function
|
|
996
|
-
async function run(context: TaskContext<EchoParams>): Promise<EchoResult> {
|
|
997
|
-
const { params, exec, log } = context;
|
|
998
|
-
// Provide a default for args in case it's not passed from the CLI
|
|
999
|
-
const { args = [] } = params;
|
|
1000
|
-
|
|
1001
|
-
log(Verbosity.DEBUG as LogLevel, `Echoing arguments: ${args.join(' ')}`);
|
|
1002
|
-
|
|
1003
|
-
// Use context.exec with an array of command and arguments
|
|
1004
|
-
// This is generally safer than manually joining strings for shell execution.
|
|
1005
|
-
const commandArray = ['echo', ...args];
|
|
1006
|
-
const { stdout } = await exec(commandArray);
|
|
1007
|
-
|
|
1008
|
-
return {
|
|
1009
|
-
stdout, // Return the standard output of the echo command
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// 4. Export the task with a dynamic description
|
|
1014
|
-
// The '{{ args?.join(" ") }}' uses Handlebars templating.
|
|
1015
|
-
// 'args' needs to be available in the template context for this to work.
|
|
1016
|
-
export default task(run, "Echoes the provided arguments: {{ args?.join(' ') }}");
|
|
1017
|
-
```
|
|
1018
|
-
|
|
1019
|
-
To run this echo task:
|
|
1020
|
-
`hostctl run path/to/echo.ts args:hello,world`
|
|
1021
|
-
Inside the `run` function, `context.params` would be `{ args: ["hello", "world"] }`.
|
|
1022
|
-
|
|
1023
1035
|
## TODO
|
|
1024
1036
|
|
|
1025
1037
|
- [ ] Further refine the script execution interface.
|