Compare commits

...

2 Commits

Author SHA1 Message Date
07692fe0bd holy shit i can score an event properly!!!! this is awesome 2026-06-29 14:40:02 +01:00
7ae5b2fbbc ignored .devenv 2026-06-29 13:37:12 +01:00
45 changed files with 96 additions and 44552 deletions

View File

@@ -1 +0,0 @@
/nix/store/cgjr3kj3hs7ngznyws5qfg16c8scpys0-bash-interactive-5.3p9

View File

@@ -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;
};
}

View File

@@ -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

View File

@@ -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;
};
}

View File

@@ -1 +0,0 @@
/nix/store/9dswnx96sj7qpqvah77lx8g25hsl1z1x-devenv-shell

View File

@@ -1 +0,0 @@
/nix/store/gj888l55lxj0brzhkjrdcald7zw7pskj-tasks.json

View File

View File

@@ -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

View File

@@ -1 +0,0 @@

View File

@@ -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);
}

View File

@@ -1 +0,0 @@
/nix/store/z7jz33yvsqvfv3qxpy2r9mp3mh0ngcvg-devenv-profile

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -1 +0,0 @@
{"managedFiles":[]}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -27,3 +27,4 @@ vite.config.ts.timestamp-*
.session .session
local.db local.db
/.devenv /.devenv
/.devenv

15
;
View File

@@ -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";
};
};
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>

View File

@@ -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');
} }
} }

View File

@@ -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();

View File

@@ -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) => {