Compare commits
2 Commits
ed98690bb6
...
07692fe0bd
| Author | SHA1 | Date | |
|---|---|---|---|
| 07692fe0bd | |||
| 7ae5b2fbbc |
@@ -1 +0,0 @@
|
|||||||
/nix/store/cgjr3kj3hs7ngznyws5qfg16c8scpys0-bash-interactive-5.3p9
|
|
||||||
@@ -1,603 +0,0 @@
|
|||||||
# Shared library functions for devenv evaluation
|
|
||||||
{ inputs }:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
# Helper to get overlays for a given input
|
|
||||||
getOverlays =
|
|
||||||
inputName: inputAttrs:
|
|
||||||
let
|
|
||||||
lib = inputs.nixpkgs.lib;
|
|
||||||
in
|
|
||||||
map
|
|
||||||
(
|
|
||||||
overlay:
|
|
||||||
let
|
|
||||||
input =
|
|
||||||
inputs.${inputName} or (throw "No such input `${inputName}` while trying to configure overlays.");
|
|
||||||
in
|
|
||||||
input.overlays.${overlay}
|
|
||||||
or (throw "Input `${inputName}` has no overlay called `${overlay}`. Supported overlays: ${lib.concatStringsSep ", " (builtins.attrNames input.overlays)}")
|
|
||||||
) inputAttrs.overlays or [ ];
|
|
||||||
|
|
||||||
# Main function to create devenv configuration for a specific system with profiles support
|
|
||||||
# This is the full-featured version used by default.nix
|
|
||||||
mkDevenvForSystem =
|
|
||||||
{ version
|
|
||||||
, is_development_version ? false
|
|
||||||
, require_version_match ? false
|
|
||||||
, system
|
|
||||||
, devenv_root
|
|
||||||
, git_root ? null
|
|
||||||
, devenv_dotfile
|
|
||||||
, devenv_dotfile_path
|
|
||||||
, devenv_tmpdir
|
|
||||||
, devenv_runtime
|
|
||||||
, devenv_state ? null
|
|
||||||
, devenv_istesting ? false
|
|
||||||
, devenv_direnvrc_latest_version
|
|
||||||
, active_profiles ? [ ]
|
|
||||||
, hostname
|
|
||||||
, username
|
|
||||||
, cli_options ? [ ]
|
|
||||||
, skip_local_src ? false
|
|
||||||
, secretspec ? null
|
|
||||||
, devenv_inputs ? { }
|
|
||||||
, devenv_imports ? [ ]
|
|
||||||
, impure ? false
|
|
||||||
, nixpkgs_config ? { }
|
|
||||||
, lock_fingerprint ? null
|
|
||||||
, primops ? { }
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
inherit (inputs) nixpkgs;
|
|
||||||
lib = nixpkgs.lib;
|
|
||||||
targetSystem = system;
|
|
||||||
|
|
||||||
overlays = lib.flatten (lib.mapAttrsToList getOverlays devenv_inputs);
|
|
||||||
|
|
||||||
# Helper to create pkgs for a given system with nixpkgs_config
|
|
||||||
mkPkgsForSystem =
|
|
||||||
evalSystem:
|
|
||||||
import nixpkgs {
|
|
||||||
system = evalSystem;
|
|
||||||
config = nixpkgs_config // {
|
|
||||||
# nixpkgs' check-meta.nix natively handles permittedInsecurePackages
|
|
||||||
# via allowInsecureDefaultPredicate using the full derivation name.
|
|
||||||
# We must NOT override allowInsecurePredicate here, as lib.getName
|
|
||||||
# strips the version, causing mismatches with user-provided entries
|
|
||||||
# like "openssl-1.1.1w".
|
|
||||||
#
|
|
||||||
# For unfree packages, nixpkgs does not natively support
|
|
||||||
# permittedUnfreePackages, so we provide a custom predicate.
|
|
||||||
allowUnfreePredicate =
|
|
||||||
if nixpkgs_config.allowUnfree or false then
|
|
||||||
(_: true)
|
|
||||||
else if (nixpkgs_config.permittedUnfreePackages or [ ]) != [ ] then
|
|
||||||
(pkg: builtins.elem (lib.getName pkg) (nixpkgs_config.permittedUnfreePackages or [ ]))
|
|
||||||
else
|
|
||||||
(_: false);
|
|
||||||
} // lib.optionalAttrs ((nixpkgs_config.allowlistedLicenses or [ ]) != [ ]) {
|
|
||||||
allowlistedLicenses = map (name: lib.licenses.${name}) (nixpkgs_config.allowlistedLicenses or [ ]);
|
|
||||||
} // lib.optionalAttrs ((nixpkgs_config.blocklistedLicenses or [ ]) != [ ]) {
|
|
||||||
blocklistedLicenses = map (name: lib.licenses.${name}) (nixpkgs_config.blocklistedLicenses or [ ]);
|
|
||||||
};
|
|
||||||
inherit overlays;
|
|
||||||
};
|
|
||||||
|
|
||||||
pkgsBootstrap = mkPkgsForSystem targetSystem;
|
|
||||||
|
|
||||||
# Helper to import a path, trying .nix first then /devenv.nix
|
|
||||||
# Returns a list of modules, including devenv.local.nix when present
|
|
||||||
tryImport =
|
|
||||||
resolvedPath: basePath:
|
|
||||||
if lib.hasSuffix ".nix" basePath then
|
|
||||||
[ (import resolvedPath) ]
|
|
||||||
else
|
|
||||||
let
|
|
||||||
devenvpath = resolvedPath + "/devenv.nix";
|
|
||||||
localpath = resolvedPath + "/devenv.local.nix";
|
|
||||||
in
|
|
||||||
if builtins.pathExists devenvpath then
|
|
||||||
[ (import devenvpath) ] ++ lib.optional (builtins.pathExists localpath) (import localpath)
|
|
||||||
else
|
|
||||||
throw (basePath + "/devenv.nix file does not exist");
|
|
||||||
|
|
||||||
importModule =
|
|
||||||
path:
|
|
||||||
if lib.hasPrefix "path:" path then
|
|
||||||
# path: prefix indicates a local filesystem path - strip it and import directly
|
|
||||||
let
|
|
||||||
actualPath = builtins.substring 5 999999 path;
|
|
||||||
in
|
|
||||||
tryImport (/. + actualPath) path
|
|
||||||
else if lib.hasPrefix "/" path then
|
|
||||||
# Absolute path - import directly (avoids input resolution and NAR hash computation)
|
|
||||||
tryImport (/. + path) path
|
|
||||||
else if lib.hasPrefix "./" path then
|
|
||||||
# Relative paths are relative to devenv_root, not bootstrap directory
|
|
||||||
let
|
|
||||||
relPath = builtins.substring 1 255 path;
|
|
||||||
in
|
|
||||||
tryImport (/. + devenv_root + relPath) path
|
|
||||||
else if lib.hasPrefix "../" path then
|
|
||||||
# Parent relative paths also relative to devenv_root
|
|
||||||
tryImport (/. + devenv_root + "/${path}") path
|
|
||||||
else
|
|
||||||
let
|
|
||||||
paths = lib.splitString "/" path;
|
|
||||||
name = builtins.head paths;
|
|
||||||
input = inputs.${name} or (throw "Unknown input ${name}");
|
|
||||||
subpath = "/${lib.concatStringsSep "/" (builtins.tail paths)}";
|
|
||||||
devenvpath = input + subpath;
|
|
||||||
in
|
|
||||||
tryImport devenvpath path;
|
|
||||||
|
|
||||||
# Common modules shared between main evaluation and cross-system evaluation
|
|
||||||
mkCommonModules =
|
|
||||||
evalPkgs:
|
|
||||||
[
|
|
||||||
(
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
_module.args.pkgs = evalPkgs.appendOverlays (config.overlays or [ ]);
|
|
||||||
_module.args.secretspec = secretspec;
|
|
||||||
_module.args.devenvPrimops = primops;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(inputs.devenv.modules + /top-level.nix)
|
|
||||||
(
|
|
||||||
{ options, ... }:
|
|
||||||
{
|
|
||||||
config.devenv = lib.mkMerge [
|
|
||||||
{
|
|
||||||
root = devenv_root;
|
|
||||||
dotfile = devenv_dotfile;
|
|
||||||
}
|
|
||||||
(
|
|
||||||
if builtins.hasAttr "cli" options.devenv then
|
|
||||||
{
|
|
||||||
cli.version = version;
|
|
||||||
cli.isDevelopment = is_development_version;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cliVersion = version;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(lib.optionalAttrs
|
|
||||||
(builtins.hasAttr "cli" options.devenv
|
|
||||||
&& builtins.hasAttr "requireVersionMatch" options.devenv.cli)
|
|
||||||
{
|
|
||||||
cli.requireVersionMatch = require_version_match;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(lib.optionalAttrs (builtins.hasAttr "tmpdir" options.devenv) {
|
|
||||||
tmpdir = devenv_tmpdir;
|
|
||||||
})
|
|
||||||
(lib.optionalAttrs (builtins.hasAttr "isTesting" options.devenv) {
|
|
||||||
isTesting = devenv_istesting;
|
|
||||||
})
|
|
||||||
(lib.optionalAttrs (builtins.hasAttr "runtime" options.devenv) {
|
|
||||||
runtime = devenv_runtime;
|
|
||||||
})
|
|
||||||
(lib.optionalAttrs (builtins.hasAttr "state" options.devenv && devenv_state != null) {
|
|
||||||
state = lib.mkForce devenv_state;
|
|
||||||
})
|
|
||||||
(lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) {
|
|
||||||
direnvrcLatestVersion = devenv_direnvrc_latest_version;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(
|
|
||||||
{ options, ... }:
|
|
||||||
{
|
|
||||||
config = lib.mkMerge [
|
|
||||||
(lib.optionalAttrs (builtins.hasAttr "git" options) {
|
|
||||||
git.root = git_root;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
++ (lib.flatten (map importModule devenv_imports))
|
|
||||||
++ (if !skip_local_src then (importModule (devenv_root + "/devenv.nix")) else [ ])
|
|
||||||
++ [
|
|
||||||
(
|
|
||||||
let
|
|
||||||
localPath = devenv_root + "/devenv.local.nix";
|
|
||||||
in
|
|
||||||
if builtins.pathExists localPath then import localPath else { }
|
|
||||||
)
|
|
||||||
cli_options
|
|
||||||
];
|
|
||||||
|
|
||||||
# Phase 1: Base evaluation to extract profile definitions
|
|
||||||
baseProject = lib.evalModules {
|
|
||||||
specialArgs = inputs // {
|
|
||||||
inherit inputs secretspec primops;
|
|
||||||
};
|
|
||||||
modules = mkCommonModules pkgsBootstrap;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Phase 2: Extract and apply profiles using extendModules with priority overrides
|
|
||||||
project =
|
|
||||||
let
|
|
||||||
# Build ordered list of profile names: hostname -> user -> manual
|
|
||||||
manualProfiles = active_profiles;
|
|
||||||
currentHostname = hostname;
|
|
||||||
currentUsername = username;
|
|
||||||
hostnameProfiles = lib.optional
|
|
||||||
(
|
|
||||||
currentHostname != null
|
|
||||||
&& currentHostname != ""
|
|
||||||
&& builtins.hasAttr currentHostname (baseProject.config.profiles.hostname or { })
|
|
||||||
) "hostname.${currentHostname}";
|
|
||||||
userProfiles = lib.optional
|
|
||||||
(
|
|
||||||
currentUsername != null
|
|
||||||
&& currentUsername != ""
|
|
||||||
&& builtins.hasAttr currentUsername (baseProject.config.profiles.user or { })
|
|
||||||
) "user.${currentUsername}";
|
|
||||||
|
|
||||||
# Ordered list of profiles to activate
|
|
||||||
orderedProfiles = hostnameProfiles ++ userProfiles ++ manualProfiles;
|
|
||||||
|
|
||||||
# Resolve profile extends with cycle detection
|
|
||||||
resolveProfileExtends =
|
|
||||||
profileName: visited:
|
|
||||||
if builtins.elem profileName visited then
|
|
||||||
throw "Circular dependency detected in profile extends: ${lib.concatStringsSep " -> " visited} -> ${profileName}"
|
|
||||||
else
|
|
||||||
let
|
|
||||||
profile = getProfileConfig profileName;
|
|
||||||
extends = profile.extends or [ ];
|
|
||||||
newVisited = visited ++ [ profileName ];
|
|
||||||
extendedProfiles = lib.flatten (map (name: resolveProfileExtends name newVisited) extends);
|
|
||||||
in
|
|
||||||
extendedProfiles ++ [ profileName ];
|
|
||||||
|
|
||||||
# Get profile configuration by name from baseProject
|
|
||||||
getProfileConfig =
|
|
||||||
profileName:
|
|
||||||
if lib.hasPrefix "hostname." profileName then
|
|
||||||
let
|
|
||||||
name = lib.removePrefix "hostname." profileName;
|
|
||||||
in
|
|
||||||
baseProject.config.profiles.hostname.${name}
|
|
||||||
else if lib.hasPrefix "user." profileName then
|
|
||||||
let
|
|
||||||
name = lib.removePrefix "user." profileName;
|
|
||||||
in
|
|
||||||
baseProject.config.profiles.user.${name}
|
|
||||||
else
|
|
||||||
let
|
|
||||||
availableProfiles = builtins.attrNames (baseProject.config.profiles or { });
|
|
||||||
hostnameProfiles = map (n: "hostname.${n}") (
|
|
||||||
builtins.attrNames (baseProject.config.profiles.hostname or { })
|
|
||||||
);
|
|
||||||
userProfiles = map (n: "user.${n}") (builtins.attrNames (baseProject.config.profiles.user or { }));
|
|
||||||
allAvailableProfiles = availableProfiles ++ hostnameProfiles ++ userProfiles;
|
|
||||||
in
|
|
||||||
baseProject.config.profiles.${profileName}
|
|
||||||
or (throw "Profile '${profileName}' not found. Available profiles: ${lib.concatStringsSep ", " allAvailableProfiles}");
|
|
||||||
|
|
||||||
# Fold over ordered profiles to build final list with extends
|
|
||||||
expandedProfiles = lib.foldl'
|
|
||||||
(
|
|
||||||
acc: profileName:
|
|
||||||
let
|
|
||||||
allProfileNames = resolveProfileExtends profileName [ ];
|
|
||||||
in
|
|
||||||
acc ++ allProfileNames
|
|
||||||
) [ ]
|
|
||||||
orderedProfiles;
|
|
||||||
|
|
||||||
# Map over expanded profiles and apply priorities
|
|
||||||
allPrioritizedModules = lib.imap0
|
|
||||||
(
|
|
||||||
index: profileName:
|
|
||||||
let
|
|
||||||
profilePriority = (lib.modules.defaultOverridePriority - 1) - index;
|
|
||||||
profileConfig = getProfileConfig profileName;
|
|
||||||
|
|
||||||
typeNeedsOverride =
|
|
||||||
type:
|
|
||||||
if type == null then
|
|
||||||
false
|
|
||||||
else
|
|
||||||
let
|
|
||||||
typeName = type.name or type._type or "";
|
|
||||||
|
|
||||||
isLeafType = builtins.elem typeName [
|
|
||||||
"str"
|
|
||||||
"int"
|
|
||||||
"bool"
|
|
||||||
"enum"
|
|
||||||
"path"
|
|
||||||
"package"
|
|
||||||
"float"
|
|
||||||
"anything"
|
|
||||||
];
|
|
||||||
in
|
|
||||||
if isLeafType then
|
|
||||||
true
|
|
||||||
else if typeName == "nullOr" then
|
|
||||||
let
|
|
||||||
innerType =
|
|
||||||
type.elemType
|
|
||||||
or (if type ? nestedTypes && type.nestedTypes ? elemType then type.nestedTypes.elemType else null);
|
|
||||||
in
|
|
||||||
if innerType != null then typeNeedsOverride innerType else false
|
|
||||||
else
|
|
||||||
false;
|
|
||||||
|
|
||||||
pathNeedsOverride =
|
|
||||||
optionPath:
|
|
||||||
let
|
|
||||||
directOption = lib.attrByPath optionPath null baseProject.options;
|
|
||||||
in
|
|
||||||
if directOption != null && lib.isOption directOption then
|
|
||||||
typeNeedsOverride directOption.type
|
|
||||||
else if optionPath != [ ] then
|
|
||||||
let
|
|
||||||
parentPath = lib.init optionPath;
|
|
||||||
parentOption = lib.attrByPath parentPath null baseProject.options;
|
|
||||||
in
|
|
||||||
if parentOption != null && lib.isOption parentOption then
|
|
||||||
let
|
|
||||||
freeformType = parentOption.type.freeformType or parentOption.type.nestedTypes.freeformType or null;
|
|
||||||
elementType =
|
|
||||||
if freeformType ? elemType then
|
|
||||||
freeformType.elemType
|
|
||||||
else if freeformType ? nestedTypes && freeformType.nestedTypes ? elemType then
|
|
||||||
freeformType.nestedTypes.elemType
|
|
||||||
else
|
|
||||||
freeformType;
|
|
||||||
in
|
|
||||||
typeNeedsOverride elementType
|
|
||||||
else
|
|
||||||
false
|
|
||||||
else
|
|
||||||
false;
|
|
||||||
|
|
||||||
applyModuleOverride =
|
|
||||||
config:
|
|
||||||
if builtins.isFunction config then
|
|
||||||
let
|
|
||||||
wrapper = args: applyOverrideRecursive (config args) [ ];
|
|
||||||
in
|
|
||||||
lib.mirrorFunctionArgs config wrapper
|
|
||||||
else
|
|
||||||
applyOverrideRecursive config [ ];
|
|
||||||
|
|
||||||
applyOverrideRecursive =
|
|
||||||
config: optionPath:
|
|
||||||
if lib.isAttrs config && config ? _type then
|
|
||||||
config
|
|
||||||
else if lib.isAttrs config then
|
|
||||||
lib.mapAttrs (name: value: applyOverrideRecursive value (optionPath ++ [ name ])) config
|
|
||||||
else if pathNeedsOverride optionPath then
|
|
||||||
lib.mkOverride profilePriority config
|
|
||||||
else
|
|
||||||
config;
|
|
||||||
|
|
||||||
prioritizedConfig = (
|
|
||||||
profileConfig.module
|
|
||||||
// {
|
|
||||||
imports = lib.map
|
|
||||||
(
|
|
||||||
importItem:
|
|
||||||
importItem
|
|
||||||
// {
|
|
||||||
imports = lib.map (nestedImport: applyModuleOverride nestedImport) (importItem.imports or [ ]);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(profileConfig.module.imports or [ ]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
in
|
|
||||||
prioritizedConfig
|
|
||||||
)
|
|
||||||
expandedProfiles;
|
|
||||||
in
|
|
||||||
if allPrioritizedModules == [ ] then
|
|
||||||
baseProject
|
|
||||||
else
|
|
||||||
baseProject.extendModules { modules = allPrioritizedModules; };
|
|
||||||
|
|
||||||
config = project.config;
|
|
||||||
|
|
||||||
# Per-container scoped re-evaluation that flips `isBuilding` for the
|
|
||||||
# container being built. Selecting one container cannot pollute the
|
|
||||||
# evaluation of any other operation, since each `containerBuilds.<name>`
|
|
||||||
# is its own `extendModules` scope.
|
|
||||||
mkContainerBuilds =
|
|
||||||
evalProject:
|
|
||||||
lib.genAttrs (lib.attrNames evalProject.config.containers) (
|
|
||||||
name:
|
|
||||||
let
|
|
||||||
scoped = evalProject.extendModules {
|
|
||||||
modules = [{
|
|
||||||
container.isBuilding = lib.mkForce true;
|
|
||||||
containers.${name}.isBuilding = lib.mkForce true;
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
scoped.config.containers.${name}
|
|
||||||
);
|
|
||||||
|
|
||||||
containerBuilds = mkContainerBuilds project;
|
|
||||||
|
|
||||||
# Apply config overlays to pkgs
|
|
||||||
pkgs = pkgsBootstrap.appendOverlays (config.overlays or [ ]);
|
|
||||||
|
|
||||||
options = pkgs.nixosOptionsDoc {
|
|
||||||
options = builtins.removeAttrs project.options [ "_module" ];
|
|
||||||
warningsAreErrors = false;
|
|
||||||
transformOptions =
|
|
||||||
let
|
|
||||||
isDocType =
|
|
||||||
v:
|
|
||||||
builtins.elem v [
|
|
||||||
"literalDocBook"
|
|
||||||
"literalExpression"
|
|
||||||
"literalMD"
|
|
||||||
"mdDoc"
|
|
||||||
];
|
|
||||||
in
|
|
||||||
lib.attrsets.mapAttrs (
|
|
||||||
_: v:
|
|
||||||
if v ? _type && isDocType v._type then
|
|
||||||
v.text
|
|
||||||
else if v ? _type && v._type == "derivation" then
|
|
||||||
v.name
|
|
||||||
else
|
|
||||||
v
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
build =
|
|
||||||
options: config:
|
|
||||||
lib.concatMapAttrs
|
|
||||||
(
|
|
||||||
name: option:
|
|
||||||
if lib.isOption option then
|
|
||||||
let
|
|
||||||
typeName = option.type.name or "";
|
|
||||||
in
|
|
||||||
if
|
|
||||||
builtins.elem typeName [
|
|
||||||
"output"
|
|
||||||
"outputOf"
|
|
||||||
]
|
|
||||||
then
|
|
||||||
{
|
|
||||||
${name} = config.${name};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ }
|
|
||||||
else if builtins.isAttrs option && !lib.isDerivation option then
|
|
||||||
let
|
|
||||||
v = build option config.${name};
|
|
||||||
in
|
|
||||||
if v != { } then { ${name} = v; } else { }
|
|
||||||
else
|
|
||||||
{ }
|
|
||||||
)
|
|
||||||
options;
|
|
||||||
|
|
||||||
# Helper to evaluate devenv for a specific system (for cross-compilation, e.g. macOS building Linux containers)
|
|
||||||
evalForSystem =
|
|
||||||
evalSystem:
|
|
||||||
let
|
|
||||||
evalPkgs = mkPkgsForSystem evalSystem;
|
|
||||||
evalProject = lib.evalModules {
|
|
||||||
specialArgs = inputs // {
|
|
||||||
inherit inputs secretspec primops;
|
|
||||||
};
|
|
||||||
modules = mkCommonModules evalPkgs;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = evalProject.config;
|
|
||||||
containerBuilds = mkContainerBuilds evalProject;
|
|
||||||
};
|
|
||||||
|
|
||||||
# All supported systems for cross-compilation (lazily evaluated)
|
|
||||||
allSystems = [
|
|
||||||
"x86_64-linux"
|
|
||||||
"aarch64-linux"
|
|
||||||
"x86_64-darwin"
|
|
||||||
"aarch64-darwin"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Generate perSystem entries for all systems (only evaluated when accessed)
|
|
||||||
perSystemConfigs = lib.genAttrs allSystems (
|
|
||||||
perSystem:
|
|
||||||
if perSystem == targetSystem then
|
|
||||||
{ inherit config containerBuilds; }
|
|
||||||
else
|
|
||||||
evalForSystem perSystem
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit
|
|
||||||
pkgs
|
|
||||||
config
|
|
||||||
options
|
|
||||||
project
|
|
||||||
;
|
|
||||||
bash = pkgs.bash;
|
|
||||||
shell = config.shell;
|
|
||||||
optionsJSON = options.optionsJSON;
|
|
||||||
info = config.info;
|
|
||||||
ci = config.ciDerivation;
|
|
||||||
build = build project.options config;
|
|
||||||
devenv = {
|
|
||||||
# Backwards compatibility: wrap config in devenv attribute for code expecting devenv.config.*
|
|
||||||
inherit config containerBuilds;
|
|
||||||
# perSystem structure for cross-compilation (e.g. macOS building Linux containers)
|
|
||||||
perSystem = perSystemConfigs;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Simplified devenv evaluation for inputs
|
|
||||||
# This is a lightweight version suitable for evaluating an input's devenv.nix
|
|
||||||
mkDevenvForInput =
|
|
||||||
{
|
|
||||||
# The input to evaluate (must have outPath and sourceInfo)
|
|
||||||
input
|
|
||||||
, # All resolved inputs (for specialArgs)
|
|
||||||
allInputs
|
|
||||||
, # System to evaluate for
|
|
||||||
system ? builtins.currentSystem
|
|
||||||
, # Nixpkgs to use (defaults to allInputs.nixpkgs)
|
|
||||||
nixpkgs ? allInputs.nixpkgs or (throw "nixpkgs input required")
|
|
||||||
, # Devenv modules (defaults to allInputs.devenv)
|
|
||||||
devenv ? allInputs.devenv or (throw "devenv input required")
|
|
||||||
,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
devenvPath = input.outPath + "/devenv.nix";
|
|
||||||
hasDevenv = builtins.pathExists devenvPath;
|
|
||||||
in
|
|
||||||
if !hasDevenv then
|
|
||||||
throw ''
|
|
||||||
Input does not have a devenv.nix file.
|
|
||||||
Expected file at: ${devenvPath}
|
|
||||||
|
|
||||||
To use this input's devenv configuration, the input must provide a devenv.nix file.
|
|
||||||
''
|
|
||||||
else
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
config = { };
|
|
||||||
};
|
|
||||||
lib = pkgs.lib;
|
|
||||||
|
|
||||||
project = lib.evalModules {
|
|
||||||
specialArgs = allInputs // {
|
|
||||||
inputs = allInputs;
|
|
||||||
secretspec = null;
|
|
||||||
};
|
|
||||||
modules = [
|
|
||||||
(
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
_module.args.pkgs = pkgs.appendOverlays (config.overlays or [ ]);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(devenv.outPath + "/src/modules/top-level.nix")
|
|
||||||
(import devenvPath)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit pkgs;
|
|
||||||
config = project.config;
|
|
||||||
options = project.options;
|
|
||||||
inherit project;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
args@{ system
|
|
||||||
, # The project root (location of devenv.nix)
|
|
||||||
devenv_root
|
|
||||||
, ...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit
|
|
||||||
(import ./resolve-lock.nix {
|
|
||||||
src = devenv_root;
|
|
||||||
inherit system;
|
|
||||||
})
|
|
||||||
inputs
|
|
||||||
;
|
|
||||||
|
|
||||||
bootstrapLib = import ./bootstrapLib.nix { inherit inputs; };
|
|
||||||
in
|
|
||||||
|
|
||||||
bootstrapLib.mkDevenvForSystem args
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
# Adapted from https://git.lix.systems/lix-project/flake-compat/src/branch/main/default.nix
|
|
||||||
{ src
|
|
||||||
, system ? builtins.currentSystem or "unknown-system"
|
|
||||||
,
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
lockFilePath = src + "/devenv.lock";
|
|
||||||
|
|
||||||
lockFile = builtins.fromJSON (builtins.readFile lockFilePath);
|
|
||||||
|
|
||||||
rootSrc = {
|
|
||||||
lastModified = 0;
|
|
||||||
lastModifiedDate = formatSecondsSinceEpoch 0;
|
|
||||||
# *hacker voice*: it's definitely a store path, I promise (actually a
|
|
||||||
# nixlang path value, likely not pointing at the store).
|
|
||||||
outPath = src;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Format number of seconds in the Unix epoch as %Y%m%d%H%M%S.
|
|
||||||
formatSecondsSinceEpoch =
|
|
||||||
t:
|
|
||||||
let
|
|
||||||
rem = x: y: x - x / y * y;
|
|
||||||
days = t / 86400;
|
|
||||||
secondsInDay = rem t 86400;
|
|
||||||
hours = secondsInDay / 3600;
|
|
||||||
minutes = (rem secondsInDay 3600) / 60;
|
|
||||||
seconds = rem t 60;
|
|
||||||
|
|
||||||
# Courtesy of https://stackoverflow.com/a/32158604.
|
|
||||||
z = days + 719468;
|
|
||||||
era = (if z >= 0 then z else z - 146096) / 146097;
|
|
||||||
doe = z - era * 146097;
|
|
||||||
yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
|
||||||
y = yoe + era * 400;
|
|
||||||
doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
||||||
mp = (5 * doy + 2) / 153;
|
|
||||||
d = doy - (153 * mp + 2) / 5 + 1;
|
|
||||||
m = mp + (if mp < 10 then 3 else -9);
|
|
||||||
y' = y + (if m <= 2 then 1 else 0);
|
|
||||||
|
|
||||||
pad = s: if builtins.stringLength s < 2 then "0" + s else s;
|
|
||||||
in
|
|
||||||
"${toString y'}${pad (toString m)}${pad (toString d)}${pad (toString hours)}${pad (toString minutes)}${pad (toString seconds)}";
|
|
||||||
|
|
||||||
allNodes = builtins.mapAttrs
|
|
||||||
(
|
|
||||||
key: node:
|
|
||||||
let
|
|
||||||
sourceInfo =
|
|
||||||
if key == lockFile.root then
|
|
||||||
rootSrc
|
|
||||||
# Path inputs pointing to project root (path = ".") should use rootSrc
|
|
||||||
# to avoid fetchTree hashing the entire project directory
|
|
||||||
else if node.locked.type or null == "path" && node.locked.path or null == "." then
|
|
||||||
rootSrc
|
|
||||||
else
|
|
||||||
let
|
|
||||||
locked = node.locked;
|
|
||||||
isRelativePath = p: p != null && (builtins.substring 0 2 p == "./" || builtins.substring 0 3 p == "../");
|
|
||||||
# Resolve relative paths against src
|
|
||||||
resolvedLocked = locked
|
|
||||||
// (if locked.type or null == "path" && isRelativePath (locked.path or null)
|
|
||||||
then { path = toString src + "/${locked.path}"; }
|
|
||||||
else { })
|
|
||||||
// (if locked.type or null == "git" && isRelativePath (locked.url or null)
|
|
||||||
then { url = toString src + "/${locked.url}"; }
|
|
||||||
else { });
|
|
||||||
in
|
|
||||||
builtins.fetchTree (node.info or { } // removeAttrs resolvedLocked [ "dir" ]);
|
|
||||||
|
|
||||||
subdir = if key == lockFile.root then "" else node.locked.dir or "";
|
|
||||||
|
|
||||||
outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir);
|
|
||||||
|
|
||||||
# Resolve a input spec into a node name. An input spec is
|
|
||||||
# either a node name, or a 'follows' path from the root
|
|
||||||
# node.
|
|
||||||
resolveInput =
|
|
||||||
inputSpec: if builtins.isList inputSpec then getInputByPath lockFile.root inputSpec else inputSpec;
|
|
||||||
|
|
||||||
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
|
|
||||||
# root node, returning the final node.
|
|
||||||
getInputByPath =
|
|
||||||
nodeName: path:
|
|
||||||
if path == [ ] then
|
|
||||||
nodeName
|
|
||||||
else
|
|
||||||
getInputByPath
|
|
||||||
# Since this could be a 'follows' input, call resolveInput.
|
|
||||||
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
|
|
||||||
(builtins.tail path);
|
|
||||||
|
|
||||||
inputs = builtins.mapAttrs (inputName: inputSpec: allNodes.${resolveInput inputSpec}) (
|
|
||||||
node.inputs or { }
|
|
||||||
);
|
|
||||||
|
|
||||||
# Only import flake.nix for non-root nodes (root doesn't need it)
|
|
||||||
flake = if key == lockFile.root then null else import (outPath + "/flake.nix");
|
|
||||||
|
|
||||||
outputs = if key == lockFile.root then { } else flake.outputs (inputs // { self = result; });
|
|
||||||
|
|
||||||
# Lazy devenv evaluation for this input
|
|
||||||
devenvEval =
|
|
||||||
let
|
|
||||||
bootstrapLib = import ./bootstrapLib.nix { inputs = inputs; };
|
|
||||||
in
|
|
||||||
bootstrapLib.mkDevenvForInput {
|
|
||||||
input = { inherit outPath sourceInfo; };
|
|
||||||
allInputs = inputs;
|
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
|
|
||||||
result =
|
|
||||||
outputs
|
|
||||||
// sourceInfo
|
|
||||||
// {
|
|
||||||
inherit outPath;
|
|
||||||
inherit inputs;
|
|
||||||
inherit outputs;
|
|
||||||
inherit sourceInfo;
|
|
||||||
_type = "flake";
|
|
||||||
devenv = devenvEval;
|
|
||||||
};
|
|
||||||
|
|
||||||
nonFlakeResult = sourceInfo // {
|
|
||||||
inherit outPath;
|
|
||||||
inherit inputs;
|
|
||||||
inherit sourceInfo;
|
|
||||||
_type = "flake";
|
|
||||||
devenv = devenvEval;
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
if node.flake or true && key != lockFile.root then
|
|
||||||
assert builtins.isFunction flake.outputs;
|
|
||||||
result
|
|
||||||
else
|
|
||||||
nonFlakeResult
|
|
||||||
)
|
|
||||||
lockFile.nodes;
|
|
||||||
|
|
||||||
result =
|
|
||||||
if !(builtins.pathExists lockFilePath) then
|
|
||||||
throw "${lockFilePath} does not exist"
|
|
||||||
else if lockFile.version >= 5 && lockFile.version <= 7 then
|
|
||||||
allNodes.${lockFile.root}
|
|
||||||
else
|
|
||||||
throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}";
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inputs = result.inputs or { } // {
|
|
||||||
self = result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/nix/store/9dswnx96sj7qpqvah77lx8g25hsl1z1x-devenv-shell
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/nix/store/gj888l55lxj0brzhkjrdcald7zw7pskj-tasks.json
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/home/user01/Projects/score-system/.devenv/bootstrap/bootstrapLib.nix
|
|
||||||
/home/user01/Projects/score-system/.devenv/bootstrap/default.nix
|
|
||||||
/home/user01/Projects/score-system/.devenv/bootstrap/resolve-lock.nix
|
|
||||||
/home/user01/Projects/score-system/devenv.local.nix
|
|
||||||
/home/user01/Projects/score-system/devenv.lock
|
|
||||||
/home/user01/Projects/score-system/devenv.nix
|
|
||||||
/home/user01/Projects/score-system/devenv.yaml
|
|
||||||
/home/user01/.config/nixpkgs/overlays
|
|
||||||
/home/user01/.config/nixpkgs/overlays.nix
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
let
|
|
||||||
cfg = {};
|
|
||||||
getName = pkg: (builtins.parseDrvName (pkg.name or pkg.pname or "")).name;
|
|
||||||
in cfg // {
|
|
||||||
allowUnfreePredicate =
|
|
||||||
if cfg.allowUnfree or false then
|
|
||||||
(_: true)
|
|
||||||
else if (cfg.permittedUnfreePackages or []) != [] then
|
|
||||||
(pkg: builtins.elem (getName pkg) (cfg.permittedUnfreePackages or []))
|
|
||||||
else
|
|
||||||
(_: false);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/nix/store/z7jz33yvsqvfv3qxpy2r9mp3mh0ngcvg-devenv-profile
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/run/user/1000/devenv-3f21a4e
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2250
.devenv/shell-env.sh
2250
.devenv/shell-env.sh
File diff suppressed because it is too large
Load Diff
@@ -1,163 +0,0 @@
|
|||||||
# Disable history during init so devenv internal commands don't pollute history.
|
|
||||||
set +o history
|
|
||||||
|
|
||||||
# Environment diff helpers (always defined for tracking)
|
|
||||||
|
|
||||||
# Environment diff helpers (inspired by direnv)
|
|
||||||
# Diff is stored in _DEVENV_DIFF env var (not a file) so each shell has its own state
|
|
||||||
# Uses gzip+base64 encoding for compact storage
|
|
||||||
|
|
||||||
# Variables to ignore in diff (shell internals that change dynamically)
|
|
||||||
__devenv_ignored_var() {
|
|
||||||
case "$1" in
|
|
||||||
_*|PWD|OLDPWD|SHLVL|SHELL|SHELLOPTS|BASHOPTS|BASH_*|HISTCMD|HISTFILE)
|
|
||||||
return 0 ;;
|
|
||||||
PS1|PS2|PS3|PS4|PROMPT|PROMPT_COMMAND|PROMPT_DIRTRIM)
|
|
||||||
return 0 ;;
|
|
||||||
COMP_*|READLINE_*|MAILCHECK|COLUMNS|LINES|RANDOM|SECONDS|LINENO|EPOCHSECONDS|EPOCHREALTIME|SRANDOM)
|
|
||||||
return 0 ;;
|
|
||||||
STARSHIP_*|__fish*|DIRENV_*|nix_saved_*)
|
|
||||||
return 0 ;;
|
|
||||||
*)
|
|
||||||
return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_capture_env() {
|
|
||||||
# Capture exported variables using declare -p for proper escaping
|
|
||||||
declare -p -x 2>/dev/null | LC_ALL=C sort
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_serialize_diff() {
|
|
||||||
# Serialize diff (stdin) to base64-encoded gzip
|
|
||||||
gzip -c | base64 -w0
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_deserialize_diff() {
|
|
||||||
# Deserialize diff from base64-encoded gzip to stdout
|
|
||||||
echo "$1" | base64 -d | gzip -d 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_compute_diff() {
|
|
||||||
# Compare before ($1) and current env, return diff via _DEVENV_DIFF env var
|
|
||||||
local before_file="$1"
|
|
||||||
|
|
||||||
# Create temp files
|
|
||||||
local after_file diff_content
|
|
||||||
after_file=$(mktemp)
|
|
||||||
diff_content=$(mktemp)
|
|
||||||
__devenv_capture_env > "$after_file"
|
|
||||||
|
|
||||||
# Build associative arrays for before/after
|
|
||||||
local -A before_vars after_vars
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "$line" != declare\ -x\ * ]] && continue
|
|
||||||
local vardef="${line#declare -x }"
|
|
||||||
local var="${vardef%%=*}"
|
|
||||||
[[ -z "$var" ]] && continue
|
|
||||||
__devenv_ignored_var "$var" && continue
|
|
||||||
before_vars["$var"]="$line"
|
|
||||||
done < "$before_file"
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "$line" != declare\ -x\ * ]] && continue
|
|
||||||
local vardef="${line#declare -x }"
|
|
||||||
local var="${vardef%%=*}"
|
|
||||||
[[ -z "$var" ]] && continue
|
|
||||||
__devenv_ignored_var "$var" && continue
|
|
||||||
after_vars["$var"]="$line"
|
|
||||||
done < "$after_file"
|
|
||||||
|
|
||||||
# Find PREV entries (vars that were modified or removed)
|
|
||||||
for var in "${!before_vars[@]}"; do
|
|
||||||
if [[ "${after_vars[$var]}" != "${before_vars[$var]}" ]]; then
|
|
||||||
echo "P:${before_vars[$var]}" >> "$diff_content"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Find NEXT entries (vars that were added or modified)
|
|
||||||
for var in "${!after_vars[@]}"; do
|
|
||||||
if [[ -z "${before_vars[$var]+x}" ]]; then
|
|
||||||
echo "N:$var" >> "$diff_content"
|
|
||||||
elif [[ "${after_vars[$var]}" != "${before_vars[$var]}" ]]; then
|
|
||||||
echo "N:$var" >> "$diff_content"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Serialize and store in env var
|
|
||||||
_DEVENV_DIFF=$(__devenv_serialize_diff < "$diff_content")
|
|
||||||
export _DEVENV_DIFF
|
|
||||||
|
|
||||||
rm -f "$after_file" "$diff_content"
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_apply_reverse_diff() {
|
|
||||||
# Reverse the diff: restore PREV values, unset NEXT-only vars
|
|
||||||
[[ -z "$_DEVENV_DIFF" ]] && return
|
|
||||||
|
|
||||||
local -A prev_vars
|
|
||||||
local diff_content
|
|
||||||
diff_content=$(__devenv_deserialize_diff "$_DEVENV_DIFF")
|
|
||||||
|
|
||||||
# First pass: collect and restore PREV declarations
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if [[ "$line" == P:declare\ * ]]; then
|
|
||||||
local decl="${line#P:}"
|
|
||||||
local var="${decl#declare -x }"
|
|
||||||
var="${var%%=*}"
|
|
||||||
prev_vars["$var"]=1
|
|
||||||
# Use export instead of evaluating the declare statement directly,
|
|
||||||
# because declare -x inside a function creates a local variable
|
|
||||||
# in bash 5.0+.
|
|
||||||
eval "export ${decl#declare -x }" 2>/dev/null
|
|
||||||
fi
|
|
||||||
done <<< "$diff_content"
|
|
||||||
|
|
||||||
# Second pass: unset NEXT vars that were not in PREV (added vars)
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if [[ "$line" == N:* ]]; then
|
|
||||||
local var="${line#N:}"
|
|
||||||
if [[ -z "${prev_vars[$var]+x}" ]]; then
|
|
||||||
unset "$var"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$diff_content"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Capture environment BEFORE sourcing devenv (for diff tracking)
|
|
||||||
_devenv_before_file=$(mktemp)
|
|
||||||
__devenv_capture_env > "$_devenv_before_file"
|
|
||||||
|
|
||||||
# Source the devenv environment
|
|
||||||
source "/home/user01/Projects/score-system/.devenv/shell-env.sh"
|
|
||||||
|
|
||||||
# Compute and store the initial diff in _DEVENV_DIFF env var
|
|
||||||
__devenv_compute_diff "$_devenv_before_file"
|
|
||||||
rm -f "$_devenv_before_file"
|
|
||||||
unset _devenv_before_file
|
|
||||||
|
|
||||||
# Save PATH before zsh init potentially modifies it
|
|
||||||
export _DEVENV_PATH="$PATH"
|
|
||||||
|
|
||||||
# Save original ZDOTDIR so zsh init can restore it
|
|
||||||
if [ -n "$ZDOTDIR" ]; then
|
|
||||||
export _DEVENV_REAL_ZDOTDIR="$ZDOTDIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Point ZDOTDIR to our init directory containing our .zshrc
|
|
||||||
export ZDOTDIR="/home/user01/Projects/score-system/.devenv/zsh"
|
|
||||||
|
|
||||||
# Re-enable history before exec
|
|
||||||
set -o history
|
|
||||||
|
|
||||||
# Exec into zsh (resolve via PATH if not absolute, since the devenv
|
|
||||||
# environment may have added it after this process started)
|
|
||||||
if [ ! -x "/run/current-system/sw/bin/zsh" ] && ! command -v "/run/current-system/sw/bin/zsh" >/dev/null 2>&1; then
|
|
||||||
echo "devenv: error: shell '/run/current-system/sw/bin/zsh' not found" >&2
|
|
||||||
echo "devenv: add zsh to your devenv.nix packages or set SHELL to an absolute path" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
exec "/run/current-system/sw/bin/zsh" -i
|
|
||||||
echo "devenv: error: failed to exec into /run/current-system/sw/bin/zsh" >&2
|
|
||||||
exit 1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"managedFiles":[]}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
devenv:container:copy
|
|
||||||
devenv:enterShell
|
|
||||||
devenv:enterTest
|
|
||||||
devenv:files
|
|
||||||
devenv:files:cleanup
|
|
||||||
devenv:processes:server
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
# devenv zsh .zshenv - runs before /etc/zshrc.
|
|
||||||
# Prepend devenv profile site-functions so the system compinit (often
|
|
||||||
# called from /etc/zshrc on nix-darwin, Debian, etc.) picks them up.
|
|
||||||
if [ -n "$DEVENV_PROFILE" ] && [ -d "$DEVENV_PROFILE/share/zsh/site-functions" ]; then
|
|
||||||
fpath=("$DEVENV_PROFILE/share/zsh/site-functions" $fpath)
|
|
||||||
fi
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
# devenv zsh init - restore ZDOTDIR and source user's .zshrc
|
|
||||||
|
|
||||||
if [ -n "$_DEVENV_REAL_ZDOTDIR" ]; then
|
|
||||||
ZDOTDIR="$_DEVENV_REAL_ZDOTDIR"
|
|
||||||
unset _DEVENV_REAL_ZDOTDIR
|
|
||||||
[ -f "$ZDOTDIR/.zshenv" ] && source "$ZDOTDIR/.zshenv"
|
|
||||||
[ -f "$ZDOTDIR/.zshrc" ] && source "$ZDOTDIR/.zshrc"
|
|
||||||
else
|
|
||||||
unset ZDOTDIR
|
|
||||||
[ -f "$HOME/.zshenv" ] && source "$HOME/.zshenv"
|
|
||||||
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Restore devenv PATH after user's .zshrc may have modified it
|
|
||||||
export PATH="$_DEVENV_PATH"
|
|
||||||
|
|
||||||
# Set devenv prompt prefix
|
|
||||||
PROMPT="(devenv) ${PROMPT}"
|
|
||||||
|
|
||||||
# Hot-reload hook
|
|
||||||
|
|
||||||
autoload -Uz add-zsh-hook
|
|
||||||
|
|
||||||
__devenv_reload_apply() {
|
|
||||||
# Source new environment if a reload is pending
|
|
||||||
if [ -f "/tmp/devenv-reload-274471.sh" ]; then
|
|
||||||
# Shell out to bash to handle the env diff (bash syntax)
|
|
||||||
local reload_output
|
|
||||||
reload_output=$(bash -c '
|
|
||||||
|
|
||||||
# Environment diff helpers (inspired by direnv)
|
|
||||||
# Diff is stored in _DEVENV_DIFF env var (not a file) so each shell has its own state
|
|
||||||
# Uses gzip+base64 encoding for compact storage
|
|
||||||
|
|
||||||
# Variables to ignore in diff (shell internals that change dynamically)
|
|
||||||
__devenv_ignored_var() {
|
|
||||||
case "$1" in
|
|
||||||
_*|PWD|OLDPWD|SHLVL|SHELL|SHELLOPTS|BASHOPTS|BASH_*|HISTCMD|HISTFILE)
|
|
||||||
return 0 ;;
|
|
||||||
PS1|PS2|PS3|PS4|PROMPT|PROMPT_COMMAND|PROMPT_DIRTRIM)
|
|
||||||
return 0 ;;
|
|
||||||
COMP_*|READLINE_*|MAILCHECK|COLUMNS|LINES|RANDOM|SECONDS|LINENO|EPOCHSECONDS|EPOCHREALTIME|SRANDOM)
|
|
||||||
return 0 ;;
|
|
||||||
STARSHIP_*|__fish*|DIRENV_*|nix_saved_*)
|
|
||||||
return 0 ;;
|
|
||||||
*)
|
|
||||||
return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_capture_env() {
|
|
||||||
# Capture exported variables using declare -p for proper escaping
|
|
||||||
declare -p -x 2>/dev/null | LC_ALL=C sort
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_serialize_diff() {
|
|
||||||
# Serialize diff (stdin) to base64-encoded gzip
|
|
||||||
gzip -c | base64 -w0
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_deserialize_diff() {
|
|
||||||
# Deserialize diff from base64-encoded gzip to stdout
|
|
||||||
echo "$1" | base64 -d | gzip -d 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_compute_diff() {
|
|
||||||
# Compare before ($1) and current env, return diff via _DEVENV_DIFF env var
|
|
||||||
local before_file="$1"
|
|
||||||
|
|
||||||
# Create temp files
|
|
||||||
local after_file diff_content
|
|
||||||
after_file=$(mktemp)
|
|
||||||
diff_content=$(mktemp)
|
|
||||||
__devenv_capture_env > "$after_file"
|
|
||||||
|
|
||||||
# Build associative arrays for before/after
|
|
||||||
local -A before_vars after_vars
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "$line" != declare\ -x\ * ]] && continue
|
|
||||||
local vardef="${line#declare -x }"
|
|
||||||
local var="${vardef%%=*}"
|
|
||||||
[[ -z "$var" ]] && continue
|
|
||||||
__devenv_ignored_var "$var" && continue
|
|
||||||
before_vars["$var"]="$line"
|
|
||||||
done < "$before_file"
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "$line" != declare\ -x\ * ]] && continue
|
|
||||||
local vardef="${line#declare -x }"
|
|
||||||
local var="${vardef%%=*}"
|
|
||||||
[[ -z "$var" ]] && continue
|
|
||||||
__devenv_ignored_var "$var" && continue
|
|
||||||
after_vars["$var"]="$line"
|
|
||||||
done < "$after_file"
|
|
||||||
|
|
||||||
# Find PREV entries (vars that were modified or removed)
|
|
||||||
for var in "${!before_vars[@]}"; do
|
|
||||||
if [[ "${after_vars[$var]}" != "${before_vars[$var]}" ]]; then
|
|
||||||
echo "P:${before_vars[$var]}" >> "$diff_content"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Find NEXT entries (vars that were added or modified)
|
|
||||||
for var in "${!after_vars[@]}"; do
|
|
||||||
if [[ -z "${before_vars[$var]+x}" ]]; then
|
|
||||||
echo "N:$var" >> "$diff_content"
|
|
||||||
elif [[ "${after_vars[$var]}" != "${before_vars[$var]}" ]]; then
|
|
||||||
echo "N:$var" >> "$diff_content"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Serialize and store in env var
|
|
||||||
_DEVENV_DIFF=$(__devenv_serialize_diff < "$diff_content")
|
|
||||||
export _DEVENV_DIFF
|
|
||||||
|
|
||||||
rm -f "$after_file" "$diff_content"
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_apply_reverse_diff() {
|
|
||||||
# Reverse the diff: restore PREV values, unset NEXT-only vars
|
|
||||||
[[ -z "$_DEVENV_DIFF" ]] && return
|
|
||||||
|
|
||||||
local -A prev_vars
|
|
||||||
local diff_content
|
|
||||||
diff_content=$(__devenv_deserialize_diff "$_DEVENV_DIFF")
|
|
||||||
|
|
||||||
# First pass: collect and restore PREV declarations
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if [[ "$line" == P:declare\ * ]]; then
|
|
||||||
local decl="${line#P:}"
|
|
||||||
local var="${decl#declare -x }"
|
|
||||||
var="${var%%=*}"
|
|
||||||
prev_vars["$var"]=1
|
|
||||||
# Use export instead of evaluating the declare statement directly,
|
|
||||||
# because declare -x inside a function creates a local variable
|
|
||||||
# in bash 5.0+.
|
|
||||||
eval "export ${decl#declare -x }" 2>/dev/null
|
|
||||||
fi
|
|
||||||
done <<< "$diff_content"
|
|
||||||
|
|
||||||
# Second pass: unset NEXT vars that were not in PREV (added vars)
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if [[ "$line" == N:* ]]; then
|
|
||||||
local var="${line#N:}"
|
|
||||||
if [[ -z "${prev_vars[$var]+x}" ]]; then
|
|
||||||
unset "$var"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$diff_content"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Reverse previous diff
|
|
||||||
__devenv_apply_reverse_diff
|
|
||||||
|
|
||||||
# Capture env before sourcing new devenv
|
|
||||||
_before=$(mktemp)
|
|
||||||
__devenv_capture_env > "$_before"
|
|
||||||
|
|
||||||
# Source new devenv environment
|
|
||||||
source "/tmp/devenv-reload-274471.sh"
|
|
||||||
rm -f "/tmp/devenv-reload-274471.sh"
|
|
||||||
|
|
||||||
# Compute new diff
|
|
||||||
__devenv_compute_diff "$_before"
|
|
||||||
rm -f "$_before"
|
|
||||||
|
|
||||||
# Output current environment for the calling shell to parse
|
|
||||||
export -p
|
|
||||||
' 2>/dev/null)
|
|
||||||
|
|
||||||
# Apply the environment changes
|
|
||||||
if [ -n "$reload_output" ]; then
|
|
||||||
eval "$reload_output"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update saved PATH
|
|
||||||
_DEVENV_PATH="$PATH"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_restore_path() {
|
|
||||||
# Restore devenv PATH (in case direnv or other tools modified it)
|
|
||||||
export PATH="$_DEVENV_PATH"
|
|
||||||
}
|
|
||||||
|
|
||||||
__devenv_precmd_hook() {
|
|
||||||
__devenv_reload_apply
|
|
||||||
__devenv_restore_path
|
|
||||||
}
|
|
||||||
add-zsh-hook precmd __devenv_precmd_hook
|
|
||||||
|
|
||||||
# Keybinding for manual reload
|
|
||||||
__devenv_reload_widget() {
|
|
||||||
__devenv_reload_apply
|
|
||||||
zle reset-prompt
|
|
||||||
}
|
|
||||||
zle -N __devenv_reload_widget
|
|
||||||
bindkey "${DEVENV_RELOAD_KEYBIND:-\\e\\C-r}" __devenv_reload_widget
|
|
||||||
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ vite.config.ts.timestamp-*
|
|||||||
.session
|
.session
|
||||||
local.db
|
local.db
|
||||||
/.devenv
|
/.devenv
|
||||||
|
/.devenv
|
||||||
|
|||||||
15
;
15
;
@@ -1,15 +0,0 @@
|
|||||||
{ pkgs, config, ... }: {
|
|
||||||
languages.typescript.enable = true;
|
|
||||||
dotenv.disableHint = true;
|
|
||||||
packages = with pkgs; [
|
|
||||||
bun
|
|
||||||
eslint_d
|
|
||||||
];
|
|
||||||
env.DEVSHELL_NAME = " devenv/#fab387| Bun/yellow";
|
|
||||||
processes = {
|
|
||||||
server = {
|
|
||||||
ports.http.allocate = 5173;
|
|
||||||
exec = "bun run dev";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -107,6 +107,7 @@ export async function getAllRegisteredEventPlayers(eventId: number) {
|
|||||||
id: players.playerId,
|
id: players.playerId,
|
||||||
firstName: players.firstName,
|
firstName: players.firstName,
|
||||||
lastName: players.lastName,
|
lastName: players.lastName,
|
||||||
|
registeredPlayerId: players.registeredPlayerId,
|
||||||
placement: players.placement,
|
placement: players.placement,
|
||||||
bracket: players.bracket,
|
bracket: players.bracket,
|
||||||
eventId: players.eventId,
|
eventId: players.eventId,
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ export const registeredEventPlayersView = sqliteView('registeredEventPlayersView
|
|||||||
return qb
|
return qb
|
||||||
.select({
|
.select({
|
||||||
playerId: players.id,
|
playerId: players.id,
|
||||||
|
registeredPlayerId: registeredPlayers.id,
|
||||||
firstName: players.firstName,
|
firstName: players.firstName,
|
||||||
lastName: players.lastName,
|
lastName: players.lastName,
|
||||||
placement: registeredPlayers.placement,
|
placement: registeredPlayers.placement,
|
||||||
|
|||||||
@@ -177,7 +177,10 @@
|
|||||||
{:else if event.state == 2}
|
{:else if event.state == 2}
|
||||||
<span
|
<span
|
||||||
class="event-winner event-status goldman"
|
class="event-winner event-status goldman"
|
||||||
style="--winner-color:{event.winner.color}">Won By {event.winner.name}</span
|
style="--winner-color:{event.winner.color}"
|
||||||
|
>Won By {event.winner.name} at {new Date(event.completed)
|
||||||
|
.toLocaleTimeString()
|
||||||
|
.slice(0, -3)}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export async function POST({ request }: any) {
|
|||||||
return new Error();
|
return new Error();
|
||||||
}
|
}
|
||||||
let scoringPreset = eventData.events[0].scoringPreset;
|
let scoringPreset = eventData.events[0].scoringPreset;
|
||||||
console.log(scoringPreset);
|
|
||||||
|
|
||||||
// make a new main ledger entry
|
// make a new main ledger entry
|
||||||
let newLedgerEntry = await db
|
let newLedgerEntry = await db
|
||||||
@@ -45,41 +44,77 @@ export async function POST({ request }: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for every bracket and player
|
// Create a Map to collect total scores per team
|
||||||
|
const teamScores = new Map<any, number>();
|
||||||
|
|
||||||
|
// for every bracket
|
||||||
for (let bracket in responseBody.brackets) {
|
for (let bracket in responseBody.brackets) {
|
||||||
|
// for every player
|
||||||
for (let player in responseBody.brackets[bracket].players) {
|
for (let player in responseBody.brackets[bracket].players) {
|
||||||
// variable fun
|
// variable fun
|
||||||
let currentPlayer = responseBody.brackets[bracket].players[player];
|
let currentPlayer = responseBody.brackets[bracket].players[player];
|
||||||
console.log(currentPlayer);
|
|
||||||
let currentPlayerTeam = currentPlayer.teamId;
|
let currentPlayerTeam = currentPlayer.teamId;
|
||||||
let currentPlayerPosition = currentPlayer.position;
|
let currentPlayerPosition = currentPlayer.position;
|
||||||
|
|
||||||
// If they put in a score
|
// If they put in a score / result
|
||||||
if (currentPlayerPosition > 0) {
|
if (currentPlayerPosition > 0) {
|
||||||
let score = getPoints(scoringPreset, currentPlayerPosition);
|
let score = getPoints(scoringPreset, currentPlayerPosition);
|
||||||
// If their score is in the preset and they put in a score
|
// If their score is in the preset and they put in a score
|
||||||
if (currentPlayer.scores.length > 0) {
|
if (currentPlayer.scores.length > 0) {
|
||||||
if (score > 0) {
|
if (score > 0) {
|
||||||
// put the scores on the board baby
|
// Accumulate points for this team instead of inserting right away
|
||||||
// THIS SHOULDNT BE REFERENCED THIS IS INTENDED
|
const currentTeamScore = teamScores.get(currentPlayerTeam) || 0;
|
||||||
let newScoreLedgerEntry = await db
|
teamScores.set(currentPlayerTeam, currentTeamScore + score);
|
||||||
.insert(schema.scoreLedger)
|
|
||||||
.values({ ledgerID: ledgerEntryId, teamID: currentPlayerTeam, points: score });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PLACEMENT LOGIC (Left untouched)
|
||||||
let newPlayerPlacement = await db
|
let newPlayerPlacement = await db
|
||||||
.update(schema.registeredPlayers)
|
.update(schema.registeredPlayers)
|
||||||
.set({ placement: currentPlayerPosition })
|
.set({ placement: currentPlayerPosition })
|
||||||
.where(eq(schema.registeredPlayers.playerID, currentPlayer.id))
|
.where(eq(schema.registeredPlayers.id, currentPlayer.registeredPlayerId))
|
||||||
.returning();
|
.returning();
|
||||||
console.log(newPlayerPlacement);
|
console.log(newPlayerPlacement[0].placement, currentPlayer.firstName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After calculating all player scores, batch insert team scores into the ledger
|
||||||
|
if (teamScores.size > 0) {
|
||||||
|
const ledgerEntries = Array.from(teamScores.entries()).map(([teamID, points]) => ({
|
||||||
|
ledgerID: ledgerEntryId,
|
||||||
|
teamID,
|
||||||
|
points
|
||||||
|
}));
|
||||||
|
|
||||||
|
let newScores = await db.insert(schema.scoreLedger).values(ledgerEntries).returning();
|
||||||
|
console.log(newScores);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Determine the winner right here using the same Map data
|
||||||
|
let highestScore = -1;
|
||||||
|
let winningTeamId = null;
|
||||||
|
|
||||||
|
for (let [teamID, points] of teamScores.entries()) {
|
||||||
|
if (points > highestScore) {
|
||||||
|
highestScore = points;
|
||||||
|
winningTeamId = teamID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winningTeamId) {
|
||||||
|
let teamWinnerUpdate = await db
|
||||||
|
.update(schema.registeredEvents)
|
||||||
|
.set({ teamWinner: winningTeamId, state: 2, timeCompleted: Date.now() })
|
||||||
|
.where(eq(schema.registeredEvents.id, responseBody.eventId))
|
||||||
|
.returning();
|
||||||
|
console.log(teamWinnerUpdate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the frontends
|
// Update the frontends
|
||||||
globalEmitter.emit('scoreUpdate');
|
globalEmitter.emit('scoreUpdate');
|
||||||
// Return a resonse because
|
// Return a response because
|
||||||
return new Response('coolsies');
|
return new Response('coolsies');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,29 @@
|
|||||||
'Content-type': 'application/json; charset=UTF-8'
|
'Content-type': 'application/json; charset=UTF-8'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return response.json();
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Sort the players inside each bracket before returning the data
|
||||||
|
if (data && data[0] && data[0].registeredPlayers) {
|
||||||
|
data[0].registeredPlayers.forEach((bracket: any) => {
|
||||||
|
bracket.items.sort((a: any, b: any) => {
|
||||||
|
// 1. Both have no placement (placement === 0) -> keep original order
|
||||||
|
if (a.placement === 0 && b.placement === 0) return 0;
|
||||||
|
|
||||||
|
// 2. 'a' has no placement, but 'b' does -> move 'b' to the top
|
||||||
|
if (a.placement === 0) return 1;
|
||||||
|
|
||||||
|
// 3. 'a' has a placement, but 'b' doesn't -> keep 'a' on top
|
||||||
|
if (b.placement === 0) return -1;
|
||||||
|
|
||||||
|
// 4. Both have placements -> sort ascending (1st, 2nd, 3rd, etc.)
|
||||||
|
return a.placement - b.placement;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let eventDataPromise = getEventData();
|
let eventDataPromise = getEventData();
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
return { destroy: () => ro.disconnect() };
|
return { destroy: () => ro.disconnect() };
|
||||||
}
|
}
|
||||||
|
|
||||||
let eventId = params.eventId;
|
let eventId = parseInt(params.eventId);
|
||||||
let eventEndpoint: EventSource;
|
let eventEndpoint: EventSource;
|
||||||
|
|
||||||
type Player = { firstName: string; lastName: string; teamColor: string; [key: string]: any };
|
type Player = { firstName: string; lastName: string; teamColor: string; [key: string]: any };
|
||||||
@@ -93,6 +93,19 @@
|
|||||||
...bracket,
|
...bracket,
|
||||||
items: sortByScore
|
items: sortByScore
|
||||||
? [...bracket.items].sort((a, b) => {
|
? [...bracket.items].sort((a, b) => {
|
||||||
|
// 1. Check if players actually have committed scores
|
||||||
|
const scoresA = committedScores[a.id]?.filter((s) => s !== null) ?? [];
|
||||||
|
const scoresB = committedScores[b.id]?.filter((s) => s !== null) ?? [];
|
||||||
|
|
||||||
|
const hasA = scoresA.length > 0;
|
||||||
|
const hasB = scoresB.length > 0;
|
||||||
|
|
||||||
|
// 2. Handle cases where one or both don't have scores
|
||||||
|
if (!hasA && !hasB) return 0; // Neither have scores, keep order
|
||||||
|
if (!hasA) return 1; // 'a' has no score -> push down
|
||||||
|
if (!hasB) return -1; // 'b' has no score -> push down
|
||||||
|
|
||||||
|
// 3. Both have scores, fallback to standard leaderboard sorting
|
||||||
const fallback = lowerIsBetter ? Infinity : -Infinity;
|
const fallback = lowerIsBetter ? Infinity : -Infinity;
|
||||||
const sa = useAverage
|
const sa = useAverage
|
||||||
? average(committedScores[a.id] ?? [], fallback)
|
? average(committedScores[a.id] ?? [], fallback)
|
||||||
@@ -100,6 +113,7 @@
|
|||||||
const sb = useAverage
|
const sb = useAverage
|
||||||
? average(committedScores[b.id] ?? [], fallback)
|
? average(committedScores[b.id] ?? [], fallback)
|
||||||
: best(committedScores[b.id] ?? [], fallback);
|
: best(committedScores[b.id] ?? [], fallback);
|
||||||
|
|
||||||
return lowerIsBetter ? sa - sb : sb - sa;
|
return lowerIsBetter ? sa - sb : sb - sa;
|
||||||
})
|
})
|
||||||
: bracket.items
|
: bracket.items
|
||||||
@@ -238,9 +252,9 @@
|
|||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="w-full flex-col px-[2vw] text-center">
|
<div class="w-full flex-col px-[2vw] text-center">
|
||||||
<div
|
<div
|
||||||
style:background-color={event.state == 1
|
style:background-color={event.state === 1
|
||||||
? 'color-mix(in srgb, #fe640b 18%, transparent);'
|
? 'color-mix(in srgb, #fe640b 18%, transparent)'
|
||||||
: undefined}
|
: ''}
|
||||||
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
|
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
|
||||||
>
|
>
|
||||||
{event.name} - {event.division} - scoring {#if event.state == 1}- ONGOING
|
{event.name} - {event.division} - scoring {#if event.state == 1}- ONGOING
|
||||||
@@ -323,6 +337,7 @@
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="run {run + 1}"
|
placeholder="run {run + 1}"
|
||||||
|
class="text-black"
|
||||||
disabled={event.state != 1}
|
disabled={event.state != 1}
|
||||||
value={pendingScores[player.id]?.[run] ?? ''}
|
value={pendingScores[player.id]?.[run] ?? ''}
|
||||||
oninput={(e) => {
|
oninput={(e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user