hostctl 0.1.45 → 0.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -841,9 +841,7 @@ declare class App {
841
841
  private resolveTaskFnFromModule;
842
842
  parseParams(scriptArgs: string[]): Object;
843
843
  pathOfPackageJsonFile(path: string): Path | null;
844
- printRuntimeReport(): Promise<void>;
845
844
  promptPasswordInteractively(message?: string): Promise<string>;
846
- installRuntime(): Promise<void>;
847
845
  /**
848
846
  * Executes a command on the selected hosts
849
847
  *
@@ -892,8 +890,6 @@ declare class Cli {
892
890
  deriveConfigRef(suppliedConfigRef?: string): string;
893
891
  loadApp(opts: Record<string, any>): Promise<void>;
894
892
  run(): Promise<void>;
895
- handleRuntime(options: Record<string, any>, cmd: cmdr.Command): Promise<void>;
896
- handleRuntimeInstall(options: Record<string, any>, cmd: cmdr.Command): Promise<void>;
897
893
  handlePkgCreate(packageName: string, options: {
898
894
  lang: 'typescript' | 'javascript';
899
895
  }): Promise<void>;
@@ -7154,7 +7150,7 @@ declare class HostctlApi {
7154
7150
  }
7155
7151
 
7156
7152
  type TaskRegistry = {
7157
- register: (name: string, task: TaskFn) => TaskRegistry;
7153
+ register: <TParams extends TaskParams = TaskParams, TReturn extends RunFnReturnValue = RunFnReturnValue>(name: string, task: TaskFn<TParams, TReturn>) => TaskRegistry;
7158
7154
  get: (name: string) => TaskFn | undefined;
7159
7155
  tasks: () => Array<[string, TaskFn]>;
7160
7156
  has: (name: string) => boolean;
package/dist/index.js CHANGED
@@ -2718,7 +2718,7 @@ var Verbosity = {
2718
2718
  };
2719
2719
 
2720
2720
  // src/version.ts
2721
- var version = "0.1.45";
2721
+ var version = "0.1.48";
2722
2722
 
2723
2723
  // src/commands/pkg/create.ts
2724
2724
  import { promises as fs5 } from "fs";
@@ -2755,8 +2755,7 @@ var packageJsonTsTemplate = (packageName) => `{
2755
2755
  "author": "",
2756
2756
  "license": "MIT",
2757
2757
  "dependencies": {
2758
- "hostctl": "^0.1.42",
2759
- "zod": "^4.1.13"
2758
+ "hostctl": "^0.1.42"
2760
2759
  },
2761
2760
  "devDependencies": {
2762
2761
  "typescript": "^5.8.3",
@@ -2789,8 +2788,7 @@ var packageJsonJsTemplate = (packageName) => `{
2789
2788
  "author": "",
2790
2789
  "license": "MIT",
2791
2790
  "dependencies": {
2792
- "hostctl": "^0.1.42",
2793
- "zod": "^4.1.13"
2791
+ "hostctl": "^0.1.42"
2794
2792
  },
2795
2793
  "engines": {
2796
2794
  "node": ">=24"
@@ -2825,157 +2823,21 @@ function registryPrefixFromPackageName(packageName) {
2825
2823
  const normalized = withoutPrefix.replace(/[^a-zA-Z0-9]+/g, ".").replace(/^\.|\.$/g, "");
2826
2824
  return normalized || "example";
2827
2825
  }
2828
- var indexTsTemplate = (registryPrefix) => `import { createRegistry } from './registry.js';
2826
+ var indexTsTemplate = (registryPrefix) => `import { createRegistry } from 'hostctl';
2829
2827
  import hello from './tasks/hello.js';
2830
2828
 
2831
2829
  export { hello };
2832
2830
 
2833
2831
  export const registry = createRegistry().register('${registryPrefix}.hello', hello);
2834
2832
  `;
2835
- var indexJsTemplate = (registryPrefix) => `import { createRegistry } from './registry.js';
2833
+ var indexJsTemplate = (registryPrefix) => `import { createRegistry } from 'hostctl';
2836
2834
  import hello from './tasks/hello.js';
2837
2835
 
2838
2836
  export { hello };
2839
2837
 
2840
2838
  export const registry = createRegistry().register('${registryPrefix}.hello', hello);
2841
2839
  `;
2842
- var registryTsTemplate = `import type { TaskFn } from 'hostctl';
2843
-
2844
- export type TaskRegistry = {
2845
- register: (name: string, task: TaskFn) => TaskRegistry;
2846
- get: (name: string) => TaskFn | undefined;
2847
- tasks: () => Array<[string, TaskFn]>;
2848
- has: (name: string) => boolean;
2849
- names: () => string[];
2850
- size: () => number;
2851
- };
2852
-
2853
- function isTaskFnLike(candidate: unknown): candidate is TaskFn {
2854
- return (
2855
- typeof candidate === 'function' &&
2856
- !!candidate &&
2857
- 'task' in (candidate as Record<string, unknown>) &&
2858
- typeof (candidate as { task?: unknown }).task === 'object'
2859
- );
2860
- }
2861
-
2862
- export function createRegistry(): TaskRegistry {
2863
- const entries = new Map<string, TaskFn>();
2864
-
2865
- const registry: TaskRegistry = {
2866
- register(name: string, task: TaskFn) {
2867
- if (!name || typeof name !== 'string') {
2868
- throw new Error('Registry task name must be a non-empty string.');
2869
- }
2870
- if (!isTaskFnLike(task)) {
2871
- throw new Error(\`Registry task '\${name}' must be a TaskFn.\`);
2872
- }
2873
- if (entries.has(name)) {
2874
- throw new Error(\`Registry already has a task named '\${name}'.\`);
2875
- }
2876
- if (task.task) {
2877
- task.task.name = name;
2878
- }
2879
- entries.set(name, task);
2880
- return registry;
2881
- },
2882
- get(name: string) {
2883
- return entries.get(name);
2884
- },
2885
- tasks() {
2886
- return Array.from(entries.entries()).sort(([a], [b]) => a.localeCompare(b));
2887
- },
2888
- has(name: string) {
2889
- return entries.has(name);
2890
- },
2891
- names() {
2892
- return Array.from(entries.keys()).sort((a, b) => a.localeCompare(b));
2893
- },
2894
- size() {
2895
- return entries.size;
2896
- },
2897
- };
2898
-
2899
- return registry;
2900
- }
2901
- `;
2902
- var registryJsTemplate = `export function createRegistry() {
2903
- const entries = new Map();
2904
-
2905
- const registry = {
2906
- register(name, task) {
2907
- if (!name || typeof name !== 'string') {
2908
- throw new Error('Registry task name must be a non-empty string.');
2909
- }
2910
- if (!task || typeof task !== 'function' || !task.task) {
2911
- throw new Error(\`Registry task '\${name}' must be a TaskFn.\`);
2912
- }
2913
- if (entries.has(name)) {
2914
- throw new Error(\`Registry already has a task named '\${name}'.\`);
2915
- }
2916
- if (task.task) {
2917
- task.task.name = name;
2918
- }
2919
- entries.set(name, task);
2920
- return registry;
2921
- },
2922
- get(name) {
2923
- return entries.get(name);
2924
- },
2925
- tasks() {
2926
- return Array.from(entries.entries()).sort(([a], [b]) => a.localeCompare(b));
2927
- },
2928
- has(name) {
2929
- return entries.has(name);
2930
- },
2931
- names() {
2932
- return Array.from(entries.keys()).sort((a, b) => a.localeCompare(b));
2933
- },
2934
- size() {
2935
- return entries.size;
2936
- },
2937
- };
2938
-
2939
- return registry;
2940
- }
2941
- `;
2942
- var taskWrapperTsTemplate = `import { task as baseTask, type RunFn, type RunFnReturnValue, type TaskFn, type TaskOptions, type TaskParams } from 'hostctl';
2943
- import type { ZodTypeAny } from 'zod';
2944
-
2945
- export type TaskOptionsWithSchema = TaskOptions & {
2946
- inputSchema?: ZodTypeAny;
2947
- outputSchema?: ZodTypeAny;
2948
- };
2949
-
2950
- export function task<TParams extends TaskParams, TReturn extends RunFnReturnValue>(
2951
- runFn: RunFn<TParams, TReturn>,
2952
- options?: TaskOptionsWithSchema,
2953
- ): TaskFn<TParams, TReturn> {
2954
- const taskFn = baseTask(runFn, options as TaskOptions);
2955
- if (options?.inputSchema) {
2956
- (taskFn.task as any).inputSchema = options.inputSchema;
2957
- }
2958
- if (options?.outputSchema) {
2959
- (taskFn.task as any).outputSchema = options.outputSchema;
2960
- }
2961
- return taskFn;
2962
- }
2963
- `;
2964
- var taskWrapperJsTemplate = `import { task as baseTask } from 'hostctl';
2965
-
2966
- export function task(runFn, options) {
2967
- const taskFn = baseTask(runFn, options);
2968
- if (options?.inputSchema) {
2969
- taskFn.task.inputSchema = options.inputSchema;
2970
- }
2971
- if (options?.outputSchema) {
2972
- taskFn.task.outputSchema = options.outputSchema;
2973
- }
2974
- return taskFn;
2975
- }
2976
- `;
2977
- var sampleTaskTsTemplate = `import { task, type TaskContext } from '../task.js';
2978
- import { z } from 'zod';
2840
+ var sampleTaskTsTemplate = `import { task, type TaskContext, z } from 'hostctl';
2979
2841
 
2980
2842
  const HelloInputSchema = z.object({
2981
2843
  name: z.string().optional(),
@@ -3008,8 +2870,7 @@ export default task(run, {
3008
2870
  outputSchema: HelloOutputSchema,
3009
2871
  });
3010
2872
  `;
3011
- var sampleTaskJsTemplate = `import { task } from '../task.js';
3012
- import { z } from 'zod';
2873
+ var sampleTaskJsTemplate = `import { task, z } from 'hostctl';
3013
2874
 
3014
2875
  const HelloInputSchema = z.object({
3015
2876
  name: z.string().optional(),
@@ -3055,7 +2916,7 @@ hostctl run . ${registryPrefix}.hello name:Hostctl excited:true
3055
2916
  - Tasks live under \`src/\` and export a default \`task(...)\`.
3056
2917
  - \`src/index.(ts|js)\` re-exports tasks and publishes a registry for discovery.
3057
2918
  - Task names are unqualified; the registry assigns qualified names.
3058
- - Schema helpers come from \`zod\` and are attached to tasks for discovery.
2919
+ - Schema helpers come from \`hostctl\` (re-exported \`zod\`) and are attached to tasks for discovery.
3059
2920
 
3060
2921
  ## Publish
3061
2922
 
@@ -3093,8 +2954,6 @@ async function createPackage(packageName, options) {
3093
2954
  await fs5.writeFile(path3.join(packageDir, "tsconfig.json"), tsconfigTemplate);
3094
2955
  await fs5.mkdir(path3.join(packageDir, "src", "tasks"), { recursive: true });
3095
2956
  await fs5.writeFile(path3.join(packageDir, "src", "index.ts"), indexTsTemplate(registryPrefix));
3096
- await fs5.writeFile(path3.join(packageDir, "src", "registry.ts"), registryTsTemplate);
3097
- await fs5.writeFile(path3.join(packageDir, "src", "task.ts"), taskWrapperTsTemplate);
3098
2957
  await fs5.writeFile(path3.join(packageDir, "src", "tasks", "hello.ts"), sampleTaskTsTemplate);
3099
2958
  await fs5.writeFile(path3.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, true));
3100
2959
  await fs5.writeFile(path3.join(packageDir, ".gitignore"), gitignoreTemplate);
@@ -3102,8 +2961,6 @@ async function createPackage(packageName, options) {
3102
2961
  await fs5.writeFile(path3.join(packageDir, "package.json"), packageJsonJsTemplate(packageJsonName));
3103
2962
  await fs5.mkdir(path3.join(packageDir, "src", "tasks"), { recursive: true });
3104
2963
  await fs5.writeFile(path3.join(packageDir, "src", "index.js"), indexJsTemplate(registryPrefix));
3105
- await fs5.writeFile(path3.join(packageDir, "src", "registry.js"), registryJsTemplate);
3106
- await fs5.writeFile(path3.join(packageDir, "src", "task.js"), taskWrapperJsTemplate);
3107
2964
  await fs5.writeFile(path3.join(packageDir, "src", "tasks", "hello.js"), sampleTaskJsTemplate);
3108
2965
  await fs5.writeFile(path3.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, false));
3109
2966
  await fs5.writeFile(path3.join(packageDir, ".gitignore"), gitignoreTemplate);
@@ -3944,15 +3801,24 @@ var PackageManager = class {
3944
3801
  }
3945
3802
  async ensurePackagesDirHasPackageJson(packagesDir) {
3946
3803
  const packageJsonPath = packagesDir.join("package.json");
3947
- if (!await packageJsonPath.exists()) {
3948
- const packageJson = {
3804
+ const hostctlOverride = process.env.HOSTCTL_PKG_HOSTCTL_OVERRIDE;
3805
+ const overrideValue = hostctlOverride ? hostctlOverride.startsWith("file:") ? hostctlOverride : `file:${Path.new(hostctlOverride).absolute().toString()}` : void 0;
3806
+ let packageJson;
3807
+ if (await packageJsonPath.exists()) {
3808
+ const raw = await fs6.readFile(packageJsonPath.toString(), "utf-8");
3809
+ packageJson = JSON.parse(raw);
3810
+ } else {
3811
+ packageJson = {
3949
3812
  name: "hostctl-packages",
3950
3813
  version: "1.0.0",
3951
3814
  description: "Hostctl package management directory",
3952
3815
  private: true
3953
3816
  };
3954
- await fs6.writeFile(packageJsonPath.toString(), JSON.stringify(packageJson, null, 2));
3955
3817
  }
3818
+ if (overrideValue) {
3819
+ packageJson.overrides = { ...packageJson.overrides ?? {}, hostctl: overrideValue };
3820
+ }
3821
+ await fs6.writeFile(packageJsonPath.toString(), JSON.stringify(packageJson, null, 2));
3956
3822
  }
3957
3823
  // Scan node_modules for the real installed package (by name or repo match)
3958
3824
  async findRealInstalledNpmPackagePath(packagesDir, source) {
@@ -4764,8 +4630,6 @@ var Cli = class {
4764
4630
  '-p, --params "<json object>"',
4765
4631
  "runtime parameters supplied as a string to be parsed as a json object representing params to supply to the script"
4766
4632
  ).option("-r, --remote", "run the script on remote hosts specified by tags via SSH orchestration").action(this.handleRun.bind(this));
4767
- const runtimeCmd = this.program.command("runtime").alias("rt").description("print out a report of the runtime environment").action(this.handleRuntime.bind(this));
4768
- runtimeCmd.command("install").alias("i").description("install a temporary runtime environment on the local host if needed").action(this.handleRuntimeInstall.bind(this));
4769
4633
  this.program.command("tasks").description("list tasks available in a package or path").argument("[package-or-path...]", "npm package spec, installed package name, or local path").option("--task <name>", "filter to a specific task name").option("--json", "output should be json formatted").action(this.handleTasksList.bind(this));
4770
4634
  }
4771
4635
  async handleInventory(options, cmd) {
@@ -4945,16 +4809,6 @@ var Cli = class {
4945
4809
  logError("hostctl error:", e);
4946
4810
  }
4947
4811
  }
4948
- async handleRuntime(options, cmd) {
4949
- const opts = cmd.optsWithGlobals();
4950
- await this.loadApp(opts);
4951
- await this.app.printRuntimeReport();
4952
- }
4953
- async handleRuntimeInstall(options, cmd) {
4954
- const opts = cmd.optsWithGlobals();
4955
- await this.loadApp(opts);
4956
- await this.app.installRuntime();
4957
- }
4958
4812
  async handlePkgCreate(packageName, options) {
4959
4813
  await createPackage(packageName, options);
4960
4814
  }
@@ -7216,15 +7070,6 @@ ${successfullyUsedIdentityPaths}`);
7216
7070
  p = p.parent();
7217
7071
  }
7218
7072
  }
7219
- async printRuntimeReport() {
7220
- const nodeRuntime = new NodeRuntime(this.tmpDir);
7221
- const reportObj = {
7222
- nodePath: await nodeRuntime.nodePath(),
7223
- npmPath: await nodeRuntime.npmPath()
7224
- // summary: report?.getReport(),
7225
- };
7226
- this.info(reportObj);
7227
- }
7228
7073
  async promptPasswordInteractively(message = "Enter your password") {
7229
7074
  if (this.providedPassword !== void 0) {
7230
7075
  return this.providedPassword;
@@ -7243,11 +7088,6 @@ ${successfullyUsedIdentityPaths}`);
7243
7088
  this.providedPassword = await promptPassword({ message });
7244
7089
  return this.providedPassword;
7245
7090
  }
7246
- async installRuntime() {
7247
- this.info(`creating node runtime with ${this.tmpDir.toString()}`);
7248
- const nodeRuntime = new NodeRuntime(this.tmpDir);
7249
- await nodeRuntime.installIfNeeded();
7250
- }
7251
7091
  /**
7252
7092
  * Executes a command on the selected hosts
7253
7093
  *
@@ -7437,23 +7277,24 @@ function isTaskFnLike2(candidate) {
7437
7277
  }
7438
7278
  function createRegistry() {
7439
7279
  const entries = /* @__PURE__ */ new Map();
7280
+ function register(name, task6) {
7281
+ if (!name || typeof name !== "string") {
7282
+ throw new Error("Registry task name must be a non-empty string.");
7283
+ }
7284
+ if (!isTaskFnLike2(task6)) {
7285
+ throw new Error(`Registry task '${name}' must be a TaskFn.`);
7286
+ }
7287
+ if (entries.has(name)) {
7288
+ throw new Error(`Registry already has a task named '${name}'.`);
7289
+ }
7290
+ if (task6.task) {
7291
+ task6.task.name = name;
7292
+ }
7293
+ entries.set(name, task6);
7294
+ return registry2;
7295
+ }
7440
7296
  const registry2 = {
7441
- register(name, task6) {
7442
- if (!name || typeof name !== "string") {
7443
- throw new Error("Registry task name must be a non-empty string.");
7444
- }
7445
- if (!isTaskFnLike2(task6)) {
7446
- throw new Error(`Registry task '${name}' must be a TaskFn.`);
7447
- }
7448
- if (entries.has(name)) {
7449
- throw new Error(`Registry already has a task named '${name}'.`);
7450
- }
7451
- if (task6.task) {
7452
- task6.task.name = name;
7453
- }
7454
- entries.set(name, task6);
7455
- return registry2;
7456
- },
7297
+ register,
7457
7298
  get(name) {
7458
7299
  return entries.get(name);
7459
7300
  },
@@ -33599,22 +33440,25 @@ function resolvedTaskName(task6) {
33599
33440
  }
33600
33441
  return deriveCoreTaskName(task6) ?? taskName ?? null;
33601
33442
  }
33602
- function collectTasksFrom(source, tasks, seen) {
33443
+ function collectTasksFrom(source, tasks, seen, pathParts = []) {
33603
33444
  if (!source) return;
33604
33445
  if (isTaskFnLike3(source)) {
33605
33446
  if (!seen.has(source)) {
33447
+ if (!hasExplicitName(source) && pathParts.length > 0) {
33448
+ source.task.name = `core.${pathParts.join(".")}`;
33449
+ }
33606
33450
  tasks.push(source);
33607
33451
  seen.add(source);
33608
33452
  }
33609
33453
  return;
33610
33454
  }
33611
33455
  if (Array.isArray(source)) {
33612
- source.forEach((entry) => collectTasksFrom(entry, tasks, seen));
33456
+ source.forEach((entry) => collectTasksFrom(entry, tasks, seen, pathParts));
33613
33457
  return;
33614
33458
  }
33615
33459
  if (isTraversableObject(source)) {
33616
- for (const value of Object.values(source)) {
33617
- collectTasksFrom(value, tasks, seen);
33460
+ for (const [key, value] of Object.entries(source)) {
33461
+ collectTasksFrom(value, tasks, seen, [...pathParts, key]);
33618
33462
  }
33619
33463
  }
33620
33464
  }