heximon 0.7.1 → 0.8.0

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.mjs +84 -26
  3. package/package.json +17 -15
package/README.md CHANGED
@@ -78,7 +78,7 @@ const main = defineCommand({
78
78
  | `heximon provision` | Creates a resource your build needs an identifier for, before the build runs. |
79
79
  | `heximon migrations` | Runs `generate`, `migrate`, `status`, or `push` against your database, with no hand-written `drizzle-kit` config. |
80
80
  | `heximon workflows` | Compares a durable workflow's current shape against its committed upgrade baseline. |
81
- | `heximon task run <Module> <Task>` | Runs one task against the production graph of the app, prints its result as JSON, and exits. It starts no HTTP listener, no schedule timer, no polling loop, and no outbox relay (`--with-outbox-relay` starts it). It refuses a graph that needs platform bindings. `--payload` sets the event payload, and `--env-file` loads variables. |
81
+ | `heximon task run <Module> <Task>` | Runs one task against the production graph of the app, prints its result as JSON, and exits. It starts no HTTP listener, no schedule timer, no polling loop, and no outbox relay (`--with-outbox-relay` starts it). It refuses a graph that needs platform bindings. The module can be a packed library module: the command runs the tasks that the module exports. `--payload` sets the event payload, and `--env-file` loads variables. Exit status: `0` when the task returned, `2` when the task itself threw (stdout then holds one `{"error":{"name":…,"message":…}}` line), `1` for every other failure. |
82
82
  | `heximon skills` | `add <url>` records an agent-skill source in `package.json` (`heximon.skills`) and installs it. `sync` re-installs every recorded source. |
83
83
  | `heximon complete <shell>` | Installs shell completion for `zsh`, `bash`, `fish`, `powershell`, or `fig`. |
84
84
 
package/dist/bin.mjs CHANGED
@@ -3467,8 +3467,9 @@ var TaskTargetError = class extends Error {
3467
3467
  };
3468
3468
  /**
3469
3469
  * Picks the module class and the task class that `heximon task run <ModuleClass> <TaskClass>` names, by class
3470
- * name, in the compiled graph. A name that matches nothing or more than one class fails with the names that
3471
- * come close.
3470
+ * name, in the compiled graph. A packed library module is a boundary of the graph: the command reaches only the
3471
+ * tasks that it exports. A name that matches nothing or more than one class fails with the names that come
3472
+ * close.
3472
3473
  */
3473
3474
  var TaskTargetResolver = class TaskTargetResolver {
3474
3475
  /** The most edits between a typed name and a suggestion. */
@@ -3478,20 +3479,24 @@ var TaskTargetResolver = class TaskTargetResolver {
3478
3479
  /**
3479
3480
  * @param graph - The module graph of the production compile.
3480
3481
  * @param moduleName - The module class name.
3481
- * @param taskName - The task class name; the module must provide it.
3482
+ * @param taskName - The task class name; the module must provide it (a library module: export it).
3483
+ * @param boundaries - The packed library modules of the graph.
3482
3484
  * @returns The module and the task.
3483
3485
  * @throws {TaskTargetError} When a name matches no class, or more than one.
3484
3486
  */
3485
- static resolve(graph, moduleName, taskName) {
3486
- const modules = TaskTargetResolver.distinct(graph.modules.map((node) => node.reference), moduleName);
3487
- if (modules.length === 0) throw new TaskTargetError(`No module class named "${moduleName}" is in the compiled graph.` + TaskTargetResolver.suggestions(moduleName, graph.modules.map((node) => TaskTargetResolver.nameOf(node.reference)), "Modules in the graph"));
3487
+ static resolve(graph, moduleName, taskName, boundaries = []) {
3488
+ const moduleReferences = [...graph.modules.map((node) => node.reference), ...boundaries.map((boundary) => boundary.reference)];
3489
+ const modules = TaskTargetResolver.distinct(moduleReferences, moduleName);
3490
+ if (modules.length === 0) throw new TaskTargetError(`No module class named "${moduleName}" is in the compiled graph.` + TaskTargetResolver.suggestions(moduleName, moduleReferences.map((reference) => TaskTargetResolver.nameOf(reference)), "Modules in the graph"));
3488
3491
  if (modules.length > 1) throw new TaskTargetError(`More than one module class is named "${moduleName}":\n` + modules.map((reference) => ` - ${reference.sourceFile}`).join("\n"));
3489
3492
  const module = modules[0];
3490
3493
  const nodes = graph.modules.filter((node) => TaskTargetResolver.isSame(node.reference, module));
3491
- const providerTokens = nodes.flatMap((node) => node.providers.map((provider) => provider.token));
3492
- const tasks = TaskTargetResolver.distinct(providerTokens, taskName);
3493
- if (tasks.length === 0) throw new TaskTargetError(`The module ${moduleName} does not provide a class named "${taskName}".` + TaskTargetResolver.otherOwner(graph, nodes, taskName) + TaskTargetResolver.suggestions(taskName, providerTokens.map((token) => TaskTargetResolver.nameOf(token)), `Providers of ${moduleName}`));
3494
- if (tasks.length > 1) throw new TaskTargetError(`The module ${moduleName} provides more than one class named "${taskName}":\n` + tasks.map((reference) => ` - ${reference.sourceFile}`).join("\n"));
3494
+ const libraries = boundaries.filter((boundary) => TaskTargetResolver.isSame(boundary.reference, module));
3495
+ const isLibrary = nodes.length === 0 && libraries.length > 0;
3496
+ const candidates = isLibrary ? libraries.flatMap((boundary) => boundary.exports.map((exported) => exported.token)) : nodes.flatMap((node) => node.providers.map((provider) => provider.token));
3497
+ const tasks = TaskTargetResolver.distinct(candidates, taskName);
3498
+ if (tasks.length === 0) throw new TaskTargetError((isLibrary ? `The library module ${moduleName} does not export a class named "${taskName}". \`heximon task run\` reaches the tasks that a library module exports: export the task from the module and pack the library again.` : `The module ${moduleName} does not provide a class named "${taskName}".`) + TaskTargetResolver.otherOwner(graph, boundaries, module, taskName) + TaskTargetResolver.suggestions(taskName, candidates.map((token) => TaskTargetResolver.nameOf(token)), isLibrary ? `Exports of ${moduleName}` : `Providers of ${moduleName}`));
3499
+ if (tasks.length > 1) throw new TaskTargetError(`The module ${moduleName} ${isLibrary ? "exports" : "provides"} more than one class named "${taskName}":\n` + tasks.map((reference) => ` - ${reference.sourceFile}`).join("\n"));
3495
3500
  return {
3496
3501
  module,
3497
3502
  task: tasks[0]
@@ -3511,11 +3516,11 @@ var TaskTargetResolver = class TaskTargetResolver {
3511
3516
  for (const reference of references) if (TaskTargetResolver.nameOf(reference) === name && !found.some((known) => TaskTargetResolver.isSame(known, reference))) found.push(reference);
3512
3517
  return found;
3513
3518
  }
3514
- /** A hint when another module of the graph provides the class. */
3515
- static otherOwner(graph, own, taskName) {
3516
- const owners = graph.modules.filter((node) => !own.includes(node) && node.providers.some((provider) => TaskTargetResolver.nameOf(provider.token) === taskName));
3519
+ /** A hint when another module of the graph provides (or a library module exports) the class. */
3520
+ static otherOwner(graph, boundaries, own, taskName) {
3521
+ const owners = [...graph.modules.filter((node) => !TaskTargetResolver.isSame(node.reference, own) && node.providers.some((provider) => TaskTargetResolver.nameOf(provider.token) === taskName)).map((node) => node.reference), ...boundaries.filter((boundary) => !TaskTargetResolver.isSame(boundary.reference, own) && boundary.exports.some((exported) => TaskTargetResolver.nameOf(exported.token) === taskName)).map((boundary) => boundary.reference)];
3517
3522
  if (owners.length === 0) return "";
3518
- return ` The class is provided by ${[...new Set(owners.map((node) => TaskTargetResolver.nameOf(node.reference)))].join(", ")}.`;
3523
+ return ` The class is provided by ${[...new Set(owners.map((reference) => TaskTargetResolver.nameOf(reference)))].join(", ")}.`;
3519
3524
  }
3520
3525
  /** The "did you mean" line, or the start of the list when nothing is close. */
3521
3526
  static suggestions(typed, candidates, listTitle) {
@@ -3654,6 +3659,8 @@ var RefusingInternalClient = class {
3654
3659
  };
3655
3660
  /** The failure a run reports to the caller; the message is ready to print. */
3656
3661
  var TaskRunFailure = class extends Error {};
3662
+ /** The task itself threw. The message is ready to print, and the thrown value is the cause. */
3663
+ var TaskRefusal = class extends TaskRunFailure {};
3657
3664
  /**
3658
3665
  * `heximon task run`: compiles the production graph into a temporary directory, boots it in this process,
3659
3666
  * runs one {@link Task}, prints its result as JSON, and removes the compile output.
@@ -3671,6 +3678,8 @@ var TaskRunner = class TaskRunner {
3671
3678
  static drainTimeoutMilliseconds = 1e4;
3672
3679
  /** The most the interrupt handler waits for the app to shut down. */
3673
3680
  static interruptShutdownMilliseconds = 5e3;
3681
+ /** The exit code of a run whose task itself threw. Every other failure exits with `1`. */
3682
+ static refusalExitCode = 2;
3674
3683
  static wiringFilePattern = /^apps\/.+\.wiring\.js$/;
3675
3684
  outputDirectory;
3676
3685
  app;
@@ -3688,14 +3697,17 @@ var TaskRunner = class TaskRunner {
3688
3697
  /** The result of the task as one line of JSON, printed after the shutdown. */
3689
3698
  serialized;
3690
3699
  disposing;
3700
+ /** How many package specifiers the run imported, for the file names of {@link ProjectModules.import}. */
3701
+ packageImports = 0;
3691
3702
  constructor(options) {
3692
3703
  this.options = options;
3693
3704
  }
3694
3705
  /**
3695
3706
  * Run the task.
3696
3707
  *
3697
- * @returns The process exit code: `0` when the task returned, `1` for every other outcome. The reason is
3698
- * already on stderr.
3708
+ * @returns The process exit code: `0` when the task returned, `2` when the task itself threw (stdout then
3709
+ * holds one `{"error":{"name":…,"message":…}}` line), `1` for every other outcome. The reason is already
3710
+ * on stderr.
3699
3711
  */
3700
3712
  async run() {
3701
3713
  let interrupts = 0;
@@ -3718,9 +3730,12 @@ var TaskRunner = class TaskRunner {
3718
3730
  await this.execute(target);
3719
3731
  } catch (error) {
3720
3732
  TaskRunner.report(error);
3721
- exitCode = 1;
3733
+ if (error instanceof TaskRefusal) {
3734
+ exitCode = TaskRunner.refusalExitCode;
3735
+ this.serialized = TaskRunner.errorLine(error.cause);
3736
+ } else exitCode = 1;
3722
3737
  } finally {
3723
- if (!await this.dispose()) exitCode = 1;
3738
+ if (!await this.dispose() && exitCode === 0) exitCode = 1;
3724
3739
  this.stdout.stop();
3725
3740
  if (this.serialized !== void 0) this.stdout.write(`${this.serialized}\n`);
3726
3741
  process.off("SIGINT", interrupt);
@@ -3793,7 +3808,7 @@ var TaskRunner = class TaskRunner {
3793
3808
  /** Pick the module and the task from the compiled graph. */
3794
3809
  pickTarget(output) {
3795
3810
  if (output.moduleGraph === void 0) throw new TaskRunFailure("The compile produced no module graph, so the task cannot be found.");
3796
- return TaskTargetResolver.resolve(output.moduleGraph, this.options.moduleName, this.options.taskName);
3811
+ return TaskTargetResolver.resolve(output.moduleGraph, this.options.moduleName, this.options.taskName, output.boundaryNodes);
3797
3812
  }
3798
3813
  /** Refuse a graph that needs bindings only its platform has, before anything boots. */
3799
3814
  static refuseBindings(output, platformName, outputDirectory) {
@@ -3837,15 +3852,34 @@ var TaskRunner = class TaskRunner {
3837
3852
  const moduleClass = await this.importClass(target.module.sourceFile, target.module.className);
3838
3853
  const taskClass = await this.importClass(target.task.sourceFile, target.task.className);
3839
3854
  if (typeof taskClass.prototype.run !== "function") throw new TaskRunFailure(`${this.options.taskName} is not a task: the class has no run() method. A task extends Task.`);
3840
- const manager = new runtime.TaskManager(this.app, this.context, await this.seedsOf(runtime));
3855
+ let task;
3856
+ try {
3857
+ task = await this.app.from(moduleClass).get(taskClass);
3858
+ } catch (error) {
3859
+ throw new TaskRunFailure(`The task ${this.options.taskName} could not be created:\n${TaskRunner.describe(error)}`, { cause: error });
3860
+ }
3861
+ const event = {
3862
+ name: this.options.taskName,
3863
+ ...this.options.payload === void 0 ? {} : { payload: this.options.payload }
3864
+ };
3865
+ const seeds = await this.seedsOf(runtime);
3841
3866
  let outcome;
3842
3867
  try {
3843
- outcome = await manager.from(moduleClass).run(taskClass, {
3844
- name: this.options.taskName,
3845
- ...this.options.payload === void 0 ? {} : { payload: this.options.payload }
3868
+ outcome = await this.context.run(async () => {
3869
+ try {
3870
+ await seeds?.apply(event, this.context);
3871
+ } catch (error) {
3872
+ throw new TaskRunFailure(`The task ${this.options.taskName} did not start:\n${TaskRunner.describe(error)}`, { cause: error });
3873
+ }
3874
+ try {
3875
+ return await task.run(event);
3876
+ } catch (error) {
3877
+ throw new TaskRefusal(`The task ${this.options.taskName} failed:\n${TaskRunner.describe(error)}`, { cause: error });
3878
+ }
3846
3879
  });
3847
3880
  } catch (error) {
3848
- throw new TaskRunFailure(`The task ${this.options.taskName} failed:\n${error instanceof Error ? error.stack ?? error.message : String(error)}`, { cause: error });
3881
+ if (error instanceof TaskRunFailure) throw error;
3882
+ throw new TaskRunFailure(`The task ${this.options.taskName} did not start:\n${TaskRunner.describe(error)}`, { cause: error });
3849
3883
  }
3850
3884
  try {
3851
3885
  this.serialized = JSON.stringify(outcome);
@@ -3863,9 +3897,13 @@ var TaskRunner = class TaskRunner {
3863
3897
  throw error;
3864
3898
  }
3865
3899
  }
3866
- /** Import a class by file (or package specifier) and export name. */
3900
+ /**
3901
+ * Import a class by file (or package specifier) and export name. A package specifier resolves from the
3902
+ * project, like the generated wiring, so the class is the one the app holds.
3903
+ */
3867
3904
  async importClass(sourceFile, className) {
3868
- const found = (await (isAbsolute(sourceFile) ? import(pathToFileURL(sourceFile).href) : import(sourceFile)))[className];
3905
+ this.packageImports += 1;
3906
+ const found = (isAbsolute(sourceFile) ? await import(pathToFileURL(sourceFile).href) : await ProjectModules.import(this.outputDirectory, `project-package-${this.packageImports}.js`, `export * from ${JSON.stringify(sourceFile)};\n`))[className];
3869
3907
  if (found === void 0) throw new TaskRunFailure(`${sourceFile} does not export a class named ${className}.`);
3870
3908
  return found;
3871
3909
  }
@@ -3916,6 +3954,26 @@ var TaskRunner = class TaskRunner {
3916
3954
  });
3917
3955
  process.exit(signal === "SIGINT" ? 130 : 143);
3918
3956
  }
3957
+ /** The text of a thrown value: the stack of an error, else its string form. */
3958
+ static describe(error) {
3959
+ return error instanceof Error ? error.stack ?? error.message : String(error);
3960
+ }
3961
+ /**
3962
+ * The one stdout line of a refused run: `{"error":{"name":…,"message":…}}`. The name is the error name, or the
3963
+ * class name when the class leaves the generic `Error` name.
3964
+ */
3965
+ static errorLine(thrown) {
3966
+ if (!(thrown instanceof Error)) return JSON.stringify({ error: {
3967
+ name: "Error",
3968
+ message: String(thrown)
3969
+ } });
3970
+ const className = thrown.constructor?.name;
3971
+ const name = thrown.name === "Error" && className !== void 0 && className !== "" ? className : thrown.name;
3972
+ return JSON.stringify({ error: {
3973
+ name,
3974
+ message: thrown.message
3975
+ } });
3976
+ }
3919
3977
  /** Print a failure on stderr. */
3920
3978
  static report(error) {
3921
3979
  if (error instanceof TaskRunFailure || error instanceof TaskTargetError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heximon",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "The Heximon CLI: create, develop, build, migrate, and deploy Heximon apps.",
5
5
  "keywords": [
6
6
  "cli",
@@ -38,25 +38,27 @@
38
38
  "dependencies": {
39
39
  "@bomb.sh/tab": "^0.0.22",
40
40
  "@clack/prompts": "^1.8.1",
41
- "@heximon/compiler": "0.7.1",
42
- "@heximon/cqrs": "0.7.1",
43
- "@heximon/deploy": "0.7.1",
44
- "@heximon/http": "0.7.1",
45
- "@heximon/node": "0.7.1",
46
- "@heximon/runtime": "0.7.1",
47
- "@heximon/testing": "0.7.1",
41
+ "@heximon/compiler": "0.8.0",
42
+ "@heximon/cqrs": "0.8.0",
43
+ "@heximon/deploy": "0.8.0",
44
+ "@heximon/http": "0.8.0",
45
+ "@heximon/node": "0.8.0",
46
+ "@heximon/runtime": "0.8.0",
47
+ "@heximon/testing": "0.8.0",
48
48
  "citty": "^0.2.2",
49
49
  "giget": "^3.3.1",
50
50
  "pathe": "^2.0.3"
51
51
  },
52
52
  "devDependencies": {
53
- "@heximon/cloudflare": "0.7.1",
54
- "@heximon/durable": "0.7.1",
55
- "@heximon/integration": "0.7.1",
56
- "@heximon/nitro": "0.7.1",
57
- "@heximon/queue": "0.7.1",
58
- "@heximon/schedule": "0.7.1",
59
- "@heximon/schema": "0.7.1",
53
+ "@heximon/cloudflare": "0.8.0",
54
+ "@heximon/durable": "0.8.0",
55
+ "@heximon/integration": "0.8.0",
56
+ "@heximon/library": "0.8.0",
57
+ "@heximon/nitro": "0.8.0",
58
+ "@heximon/queue": "0.8.0",
59
+ "@heximon/schedule": "0.8.0",
60
+ "@heximon/schema": "0.8.0",
61
+ "@heximon/test-support": "0.0.1",
60
62
  "@types/node": "^24.19.0",
61
63
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",
62
64
  "@vitest/coverage-v8": "5.0.1",