now-runner 0.2.0

Nix-based distributed command runner
# now: A Nix-based distributed command runner
# Copyright (C) 2026 Eric Rodrigues Pires
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.

{ lib }:
let
  inherit (lib) types;

  env =
    evalId:
    types.attrsOf (
      types.either types.str (
        types.attrTag {
          ${"__nowSecret_${evalId}"} = lib.mkOption { type = types.str; };
          ${"__nowDownload_${evalId}"} = lib.mkOption { type = types.str; };
        }
      )
    );

  step =
    evalId:
    types.submodule {
      options = {
        name = lib.mkOption {
          type = types.nullOr types.str;
          default = null;
          description = "Name of the step.";
        };
        shell = lib.mkOption {
          type = types.nullOr types.package;
          default = null;
          description = "The shell to use for this step.";
        };
        shellArgs = lib.mkOption {
          type = types.nullOr (types.listOf types.str);
          default = null;
          description = "Args passed to the shell used in this step.";
        };
        run = lib.mkOption {
          type = types.str;
          default = "";
          description = "Shell script to run on this step.";
        };
        teardown = lib.mkOption {
          type = types.nullOr types.str;
          default = null;
          description = "Shell script to run when tearing down this step, after every step, in reverse order.";
        };
        path = lib.mkOption {
          type = types.listOf types.package;
          default = [ ];
          description = "Packages added to the PATH of the script.";
        };
        env = lib.mkOption {
          type = env evalId;
          default = { };
          description = "Environment values to make available to this step.";
        };
        ${"__nowUpload_${evalId}"} = lib.mkOption {
          type = types.nullOr types.str;
          default = null;
        };
      };
    };

  job =
    evalId:
    types.submodule {
      options = {
        name = lib.mkOption {
          type = types.nullOr types.str;
          default = null;
          description = "Name of the job.";
        };
        strategy = lib.mkOption {
          type = types.nullOr (
            types.submodule {
              options = {
                fail-fast = lib.mkOption {
                  type = types.bool;
                  default = true;
                  description = "Whether a single failing run should cancel the remaining jobs in the matrix.";
                };
              };
            }
          );
          default = null;
        };
        needs = lib.mkOption {
          type = types.nullOr (types.listOf types.str);
          default = null;
          description = "Jobs that must be completed before running this one.";
        };
        env = lib.mkOption {
          type = env evalId;
          default = { };
          description = "Environment values to make available to steps in this job.";
        };
        steps = lib.mkOption {
          type = types.listOf (types.nullOr types.raw);
          default = [ ];
          description = "Steps to run in this job.";
        };
      };
    };
in
{
  inherit step job;
}