Merge branch 'fix/consolidate-workflows' into new

This commit is contained in:
Snider 2026-02-08 22:00:21 +00:00
commit 315b4fc052
26 changed files with 487 additions and 30 deletions

40
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: CodeQL
on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]
schedule:
- cron: "0 6 * * 1"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [go, javascript-typescript, python, actions]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"

11
go.mod
View file

@ -3,7 +3,10 @@ module github.com/host-uk/core
go 1.25.5 go 1.25.5
require ( require (
<<<<<<< HEAD
code.gitea.io/sdk/gitea v0.23.2 code.gitea.io/sdk/gitea v0.23.2
=======
>>>>>>> fix/consolidate-workflows
github.com/Snider/Borg v0.2.0 github.com/Snider/Borg v0.2.0
github.com/getkin/kin-openapi v0.133.0 github.com/getkin/kin-openapi v0.133.0
github.com/host-uk/core/internal/core-ide v0.0.0-20260204004957-989b7e1e6555 github.com/host-uk/core/internal/core-ide v0.0.0-20260204004957-989b7e1e6555
@ -36,6 +39,7 @@ require (
github.com/42wim/httpsig v1.2.3 // indirect github.com/42wim/httpsig v1.2.3 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/Snider/Enchantrix v0.0.2 // indirect
github.com/TwiN/go-color v1.4.1 // indirect github.com/TwiN/go-color v1.4.1 // indirect
github.com/adrg/xdg v0.5.3 // indirect github.com/adrg/xdg v0.5.3 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect
@ -62,8 +66,11 @@ require (
github.com/ebitengine/purego v0.9.1 // indirect github.com/ebitengine/purego v0.9.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
<<<<<<< HEAD
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect
=======
>>>>>>> fix/consolidate-workflows
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect github.com/go-git/go-billy/v5 v5.7.0 // indirect
github.com/go-git/go-git/v5 v5.16.4 // indirect github.com/go-git/go-git/v5 v5.16.4 // indirect
@ -74,6 +81,8 @@ require (
github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/flock v0.12.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-github/v39 v39.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
@ -90,6 +99,7 @@ require (
github.com/mailru/easyjson v0.9.1 // indirect github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
@ -103,6 +113,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/lo v1.52.0 // indirect github.com/samber/lo v1.52.0 // indirect
github.com/schollz/progressbar/v3 v3.18.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect github.com/skeema/knownhosts v1.3.2 // indirect

39
go.sum
View file

@ -16,8 +16,17 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
<<<<<<< HEAD
github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ= github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ=
github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY= github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY=
=======
github.com/Snider/Borg v0.1.0 h1:tLvrytPMIM2To0xByYP+KHLcT9pg9P9y9uRTyG6r9oc=
github.com/Snider/Borg v0.1.0/go.mod h1:0GMzdXYzdFZpR25IFne7ErqV/YFQHsX1THm1BbncMPo=
github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ=
github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY=
github.com/Snider/Enchantrix v0.0.2 h1:ExZQiBhfS/p/AHFTKhY80TOd+BXZjK95EzByAEgwvjs=
github.com/Snider/Enchantrix v0.0.2/go.mod h1:CtFcLAvnDT1KcuF1JBb/DJj0KplY8jHryO06KzQ1hsQ=
>>>>>>> fix/consolidate-workflows
github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc=
github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
@ -79,8 +88,11 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
<<<<<<< HEAD
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
=======
>>>>>>> fix/consolidate-workflows
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@ -123,10 +135,18 @@ github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeD
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -182,6 +202,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s= github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
@ -225,6 +247,8 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@ -303,7 +327,11 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
<<<<<<< HEAD
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
=======
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
>>>>>>> fix/consolidate-workflows
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
@ -313,10 +341,15 @@ golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHi
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
<<<<<<< HEAD
=======
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
>>>>>>> fix/consolidate-workflows
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
@ -340,6 +373,10 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
<<<<<<< HEAD
=======
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
>>>>>>> fix/consolidate-workflows
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
@ -347,8 +384,10 @@ golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=

View file

@ -15,7 +15,11 @@ import (
"strings" "strings"
"github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/cli"
<<<<<<< HEAD
errors "github.com/host-uk/core/pkg/framework/core" errors "github.com/host-uk/core/pkg/framework/core"
=======
core "github.com/host-uk/core/pkg/framework/core"
>>>>>>> fix/consolidate-workflows
"github.com/host-uk/core/pkg/git" "github.com/host-uk/core/pkg/git"
"github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/i18n"
"github.com/host-uk/core/pkg/io" "github.com/host-uk/core/pkg/io"
@ -66,19 +70,19 @@ func runApply() error {
// Validate inputs // Validate inputs
if applyCommand == "" && applyScript == "" { if applyCommand == "" && applyScript == "" {
return errors.E("dev.apply", i18n.T("cmd.dev.apply.error.no_command"), nil) return core.E("dev.apply", i18n.T("cmd.dev.apply.error.no_command"), nil)
} }
if applyCommand != "" && applyScript != "" { if applyCommand != "" && applyScript != "" {
return errors.E("dev.apply", i18n.T("cmd.dev.apply.error.both_command_script"), nil) return core.E("dev.apply", i18n.T("cmd.dev.apply.error.both_command_script"), nil)
} }
if applyCommit && applyMessage == "" { if applyCommit && applyMessage == "" {
return errors.E("dev.apply", i18n.T("cmd.dev.apply.error.commit_needs_message"), nil) return core.E("dev.apply", i18n.T("cmd.dev.apply.error.commit_needs_message"), nil)
} }
// Validate script exists // Validate script exists
if applyScript != "" { if applyScript != "" {
if !io.Local.IsFile(applyScript) { if !io.Local.IsFile(applyScript) {
return errors.E("dev.apply", "script not found: "+applyScript, nil) // Error mismatch? IsFile returns bool return core.E("dev.apply", "script not found: "+applyScript, nil) // Error mismatch? IsFile returns bool
} }
} }
@ -89,7 +93,7 @@ func runApply() error {
} }
if len(targetRepos) == 0 { if len(targetRepos) == 0 {
return errors.E("dev.apply", i18n.T("cmd.dev.apply.error.no_repos"), nil) return core.E("dev.apply", i18n.T("cmd.dev.apply.error.no_repos"), nil)
} }
// Show plan // Show plan
@ -227,12 +231,12 @@ func getApplyTargetRepos() ([]*repos.Repo, error) {
// Load registry // Load registry
registryPath, err := repos.FindRegistry(io.Local) registryPath, err := repos.FindRegistry(io.Local)
if err != nil { if err != nil {
return nil, errors.E("dev.apply", "failed to find registry", err) return nil, core.E("dev.apply", "failed to find registry", err)
} }
registry, err := repos.LoadRegistry(io.Local, registryPath) registry, err := repos.LoadRegistry(io.Local, registryPath)
if err != nil { if err != nil {
return nil, errors.E("dev.apply", "failed to load registry", err) return nil, core.E("dev.apply", "failed to load registry", err)
} }
// If --repos specified, filter to those // If --repos specified, filter to those

View file

@ -2,7 +2,6 @@ package dev
import ( import (
"bytes" "bytes"
"context"
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
@ -17,25 +16,6 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
// syncInternalToPublic handles the synchronization of internal packages to public-facing directories.
// This function is a placeholder for future implementation.
func syncInternalToPublic(ctx context.Context, publicDir string) error {
// 1. Clean public/internal
// 2. Copy relevant files from internal/ to public/internal/
// Usually just shared logic, not private stuff.
// For now, let's assume we copy specific safe packages
// Logic to be refined.
// Example migration of os calls:
// internalDirs, err := os.ReadDir(pkgDir) -> coreio.Local.List(pkgDir)
// os.Stat -> coreio.Local.IsFile (returns bool) or List for existence check
// os.MkdirAll -> coreio.Local.EnsureDir
// os.WriteFile -> coreio.Local.Write
return nil
}
// addSyncCommand adds the 'sync' command to the given parent command. // addSyncCommand adds the 'sync' command to the given parent command.
func addSyncCommand(parent *cli.Command) { func addSyncCommand(parent *cli.Command) {
syncCmd := &cli.Command{ syncCmd := &cli.Command{

View file

@ -174,6 +174,7 @@ func (s *Service) runWork(task TaskWork) error {
cli.Print(" %s: %d commits\n", st.Name, st.Ahead) cli.Print(" %s: %d commits\n", st.Name, st.Ahead)
} }
<<<<<<< HEAD
if !task.AutoPush { if !task.AutoPush {
cli.Blank() cli.Blank()
cli.Print("Push all? [y/N] ") cli.Print("Push all? [y/N] ")
@ -183,6 +184,15 @@ func (s *Service) runWork(task TaskWork) error {
cli.Println("Aborted") cli.Println("Aborted")
return nil return nil
} }
=======
cli.Blank()
cli.Print("Push all? [y/N] ")
var answer string
_, _ = cli.Scanln(&answer)
if strings.ToLower(answer) != "y" {
cli.Println("Aborted")
return nil
>>>>>>> fix/consolidate-workflows
} }
cli.Blank() cli.Blank()

View file

@ -117,7 +117,7 @@ func scanRepoDocs(repo *repos.Repo) RepoDocInfo {
docsDir := filepath.Join(repo.Path, "docs") docsDir := filepath.Join(repo.Path, "docs")
// Check if directory exists by listing it // Check if directory exists by listing it
if _, err := io.Local.List(docsDir); err == nil { if _, err := io.Local.List(docsDir); err == nil {
filepath.WalkDir(docsDir, func(path string, d fs.DirEntry, err error) error { _ = filepath.WalkDir(docsDir, func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
return nil return nil
} }

View file

@ -212,6 +212,7 @@ func addGoCovCommand(parent *cli.Command) {
} }
covPath := covFile.Name() covPath := covFile.Name()
_ = covFile.Close() _ = covFile.Close()
<<<<<<< HEAD
defer func() { defer func() {
if covOutput == "" { if covOutput == "" {
_ = os.Remove(covPath) _ = os.Remove(covPath)
@ -227,6 +228,9 @@ func addGoCovCommand(parent *cli.Command) {
_ = os.Remove(covPath) _ = os.Remove(covPath)
} }
}() }()
=======
defer func() { _ = os.Remove(covPath) }()
>>>>>>> fix/consolidate-workflows
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("coverage")), i18n.ProgressSubject("run", "tests")) cli.Print("%s %s\n", dimStyle.Render(i18n.Label("coverage")), i18n.ProgressSubject("run", "tests"))
// Truncate package list if too long for display // Truncate package list if too long for display
@ -269,7 +273,11 @@ func addGoCovCommand(parent *cli.Command) {
parts := strings.Fields(lastLine) parts := strings.Fields(lastLine)
if len(parts) >= 3 { if len(parts) >= 3 {
covStr := strings.TrimSuffix(parts[len(parts)-1], "%") covStr := strings.TrimSuffix(parts[len(parts)-1], "%")
<<<<<<< HEAD
_, _ = fmt.Sscanf(covStr, "%f", &statementCov) _, _ = fmt.Sscanf(covStr, "%f", &statementCov)
=======
_, _ = fmt.Sscanf(covStr, "%f", &totalCov)
>>>>>>> fix/consolidate-workflows
} }
} }
} }

View file

@ -150,7 +150,11 @@ func (r *QARunner) buildSpec(check string) *process.RunSpec {
phpunitBin := filepath.Join(r.dir, "vendor", "bin", "phpunit") phpunitBin := filepath.Join(r.dir, "vendor", "bin", "phpunit")
var cmd string var cmd string
<<<<<<< HEAD
if m.IsFile(pestBin) { if m.IsFile(pestBin) {
=======
if _, err := os.Stat(pestBin); err == nil {
>>>>>>> fix/consolidate-workflows
cmd = pestBin cmd = pestBin
} else if m.IsFile(phpunitBin) { } else if m.IsFile(phpunitBin) {
cmd = phpunitBin cmd = phpunitBin

View file

@ -174,6 +174,10 @@ func needsRedis(dir string) bool {
if err != nil { if err != nil {
return false return false
} }
<<<<<<< HEAD
=======
defer func() { _ = file.Close() }()
>>>>>>> fix/consolidate-workflows
lines := strings.Split(content, "\n") lines := strings.Split(content, "\n")
for _, line := range lines { for _, line := range lines {
@ -238,6 +242,10 @@ func GetLaravelAppName(dir string) string {
if err != nil { if err != nil {
return "" return ""
} }
<<<<<<< HEAD
=======
defer func() { _ = file.Close() }()
>>>>>>> fix/consolidate-workflows
lines := strings.Split(content, "\n") lines := strings.Split(content, "\n")
for _, line := range lines { for _, line := range lines {
@ -261,6 +269,10 @@ func GetLaravelAppURL(dir string) string {
if err != nil { if err != nil {
return "" return ""
} }
<<<<<<< HEAD
=======
defer func() { _ = file.Close() }()
>>>>>>> fix/consolidate-workflows
lines := strings.Split(content, "\n") lines := strings.Split(content, "\n")
for _, line := range lines { for _, line := range lines {

View file

@ -27,6 +27,9 @@ import (
// Commands via self-registration // Commands via self-registration
_ "github.com/host-uk/core/internal/cmd/ai" _ "github.com/host-uk/core/internal/cmd/ai"
_ "github.com/host-uk/core/internal/cmd/ci" _ "github.com/host-uk/core/internal/cmd/ci"
_ "github.com/host-uk/core/internal/cmd/collect"
_ "github.com/host-uk/core/internal/cmd/config"
_ "github.com/host-uk/core/internal/cmd/crypt"
_ "github.com/host-uk/core/internal/cmd/deploy" _ "github.com/host-uk/core/internal/cmd/deploy"
_ "github.com/host-uk/core/internal/cmd/dev" _ "github.com/host-uk/core/internal/cmd/dev"
_ "github.com/host-uk/core/internal/cmd/docs" _ "github.com/host-uk/core/internal/cmd/docs"
@ -37,6 +40,7 @@ import (
_ "github.com/host-uk/core/internal/cmd/monitor" _ "github.com/host-uk/core/internal/cmd/monitor"
_ "github.com/host-uk/core/internal/cmd/php" _ "github.com/host-uk/core/internal/cmd/php"
_ "github.com/host-uk/core/internal/cmd/pkgcmd" _ "github.com/host-uk/core/internal/cmd/pkgcmd"
_ "github.com/host-uk/core/internal/cmd/plugin"
_ "github.com/host-uk/core/internal/cmd/qa" _ "github.com/host-uk/core/internal/cmd/qa"
_ "github.com/host-uk/core/internal/cmd/sdk" _ "github.com/host-uk/core/internal/cmd/sdk"
_ "github.com/host-uk/core/internal/cmd/security" _ "github.com/host-uk/core/internal/cmd/security"

View file

@ -99,6 +99,10 @@ func loadEnvFile(path string, cfg *Config) error {
if err != nil { if err != nil {
return err return err
} }
<<<<<<< HEAD
=======
defer func() { _ = file.Close() }()
>>>>>>> fix/consolidate-workflows
for _, line := range strings.Split(content, "\n") { for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)

View file

@ -74,7 +74,11 @@ func (s *MacOSSigner) Notarize(ctx context.Context, fs io.Medium, binary string)
if output, err := zipCmd.CombinedOutput(); err != nil { if output, err := zipCmd.CombinedOutput(); err != nil {
return fmt.Errorf("codesign.Notarize: failed to create zip: %w\nOutput: %s", err, string(output)) return fmt.Errorf("codesign.Notarize: failed to create zip: %w\nOutput: %s", err, string(output))
} }
<<<<<<< HEAD
defer func() { _ = fs.Delete(zipPath) }() defer func() { _ = fs.Delete(zipPath) }()
=======
defer func() { _ = os.Remove(zipPath) }()
>>>>>>> fix/consolidate-workflows
// Submit to Apple and wait // Submit to Apple and wait
submitCmd := exec.CommandContext(ctx, "xcrun", "notarytool", "submit", submitCmd := exec.CommandContext(ctx, "xcrun", "notarytool", "submit",

View file

@ -13,11 +13,15 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
<<<<<<< HEAD
"path/filepath" "path/filepath"
=======
>>>>>>> fix/consolidate-workflows
"strings" "strings"
"sync" "sync"
core "github.com/host-uk/core/pkg/framework/core" core "github.com/host-uk/core/pkg/framework/core"
<<<<<<< HEAD
coreio "github.com/host-uk/core/pkg/io" coreio "github.com/host-uk/core/pkg/io"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -30,13 +34,29 @@ type Config struct {
v *viper.Viper v *viper.Viper
medium coreio.Medium medium coreio.Medium
path string path string
=======
"github.com/host-uk/core/pkg/io"
)
// Config implements the core.Config interface with layered resolution.
// Values are resolved in order: defaults -> file -> env -> flags.
type Config struct {
mu sync.RWMutex
medium io.Medium
path string
data map[string]any
>>>>>>> fix/consolidate-workflows
} }
// Option is a functional option for configuring a Config instance. // Option is a functional option for configuring a Config instance.
type Option func(*Config) type Option func(*Config)
// WithMedium sets the storage medium for configuration file operations. // WithMedium sets the storage medium for configuration file operations.
<<<<<<< HEAD
func WithMedium(m coreio.Medium) Option { func WithMedium(m coreio.Medium) Option {
=======
func WithMedium(m io.Medium) Option {
>>>>>>> fix/consolidate-workflows
return func(c *Config) { return func(c *Config) {
c.medium = m c.medium = m
} }
@ -49,6 +69,7 @@ func WithPath(path string) Option {
} }
} }
<<<<<<< HEAD
// WithEnvPrefix sets the prefix for environment variables. // WithEnvPrefix sets the prefix for environment variables.
func WithEnvPrefix(prefix string) Option { func WithEnvPrefix(prefix string) Option {
return func(c *Config) { return func(c *Config) {
@ -56,11 +77,14 @@ func WithEnvPrefix(prefix string) Option {
} }
} }
=======
>>>>>>> fix/consolidate-workflows
// New creates a new Config instance with the given options. // New creates a new Config instance with the given options.
// If no medium is provided, it defaults to io.Local. // If no medium is provided, it defaults to io.Local.
// If no path is provided, it defaults to ~/.core/config.yaml. // If no path is provided, it defaults to ~/.core/config.yaml.
func New(opts ...Option) (*Config, error) { func New(opts ...Option) (*Config, error) {
c := &Config{ c := &Config{
<<<<<<< HEAD
v: viper.New(), v: viper.New(),
} }
@ -68,12 +92,21 @@ func New(opts ...Option) (*Config, error) {
c.v.SetEnvPrefix("CORE_CONFIG") c.v.SetEnvPrefix("CORE_CONFIG")
c.v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) c.v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
=======
data: make(map[string]any),
}
>>>>>>> fix/consolidate-workflows
for _, opt := range opts { for _, opt := range opts {
opt(c) opt(c)
} }
if c.medium == nil { if c.medium == nil {
<<<<<<< HEAD
c.medium = coreio.Local c.medium = coreio.Local
=======
c.medium = io.Local
>>>>>>> fix/consolidate-workflows
} }
if c.path == "" { if c.path == "" {
@ -81,6 +114,7 @@ func New(opts ...Option) (*Config, error) {
if err != nil { if err != nil {
return nil, core.E("config.New", "failed to determine home directory", err) return nil, core.E("config.New", "failed to determine home directory", err)
} }
<<<<<<< HEAD
c.path = filepath.Join(home, ".core", "config.yaml") c.path = filepath.Join(home, ".core", "config.yaml")
} }
@ -91,11 +125,30 @@ func New(opts ...Option) (*Config, error) {
if err := c.LoadFile(c.medium, c.path); err != nil { if err := c.LoadFile(c.medium, c.path); err != nil {
return nil, core.E("config.New", "failed to load config file", err) return nil, core.E("config.New", "failed to load config file", err)
} }
=======
c.path = home + "/.core/config.yaml"
}
// Load existing config file if it exists
if c.medium.IsFile(c.path) {
loaded, err := Load(c.medium, c.path)
if err != nil {
return nil, core.E("config.New", "failed to load config file", err)
}
c.data = loaded
}
// Overlay environment variables
envData := LoadEnv("CORE_CONFIG_")
for k, v := range envData {
setNested(c.data, k, v)
>>>>>>> fix/consolidate-workflows
} }
return c, nil return c, nil
} }
<<<<<<< HEAD
// LoadFile reads a configuration file from the given medium and path and merges it into the current config. // LoadFile reads a configuration file from the given medium and path and merges it into the current config.
// It supports YAML and environment files (.env). // It supports YAML and environment files (.env).
func (c *Config) LoadFile(m coreio.Medium, path string) error { func (c *Config) LoadFile(m coreio.Medium, path string) error {
@ -126,10 +179,16 @@ func (c *Config) LoadFile(m coreio.Medium, path string) error {
// Get retrieves a configuration value by dot-notation key and stores it in out. // Get retrieves a configuration value by dot-notation key and stores it in out.
// If key is empty, it unmarshals the entire configuration into out. // If key is empty, it unmarshals the entire configuration into out.
// The out parameter must be a pointer to the target type. // The out parameter must be a pointer to the target type.
=======
// Get retrieves a configuration value by dot-notation key and stores it in out.
// The out parameter must be a pointer to the target type.
// Returns an error if the key is not found.
>>>>>>> fix/consolidate-workflows
func (c *Config) Get(key string, out any) error { func (c *Config) Get(key string, out any) error {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
<<<<<<< HEAD
if key == "" { if key == "" {
return c.v.Unmarshal(out) return c.v.Unmarshal(out)
} }
@ -139,6 +198,14 @@ func (c *Config) Get(key string, out any) error {
} }
return c.v.UnmarshalKey(key, out) return c.v.UnmarshalKey(key, out)
=======
val, ok := getNested(c.data, key)
if !ok {
return core.E("config.Get", fmt.Sprintf("key not found: %s", key), nil)
}
return assign(val, out)
>>>>>>> fix/consolidate-workflows
} }
// Set stores a configuration value by dot-notation key and persists to disk. // Set stores a configuration value by dot-notation key and persists to disk.
@ -146,10 +213,16 @@ func (c *Config) Set(key string, v any) error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
<<<<<<< HEAD
c.v.Set(key, v) c.v.Set(key, v)
// Persist to disk // Persist to disk
if err := Save(c.medium, c.path, c.v.AllSettings()); err != nil { if err := Save(c.medium, c.path, c.v.AllSettings()); err != nil {
=======
setNested(c.data, key, v)
if err := Save(c.medium, c.path, c.data); err != nil {
>>>>>>> fix/consolidate-workflows
return core.E("config.Set", "failed to save config", err) return core.E("config.Set", "failed to save config", err)
} }
@ -161,7 +234,29 @@ func (c *Config) All() map[string]any {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
<<<<<<< HEAD
return c.v.AllSettings() return c.v.AllSettings()
=======
return deepCopyMap(c.data)
}
// deepCopyMap recursively copies a map[string]any.
func deepCopyMap(src map[string]any) map[string]any {
result := make(map[string]any, len(src))
for k, v := range src {
switch val := v.(type) {
case map[string]any:
result[k] = deepCopyMap(val)
case []any:
cp := make([]any, len(val))
copy(cp, val)
result[k] = cp
default:
result[k] = v
}
}
return result
>>>>>>> fix/consolidate-workflows
} }
// Path returns the path to the configuration file. // Path returns the path to the configuration file.
@ -169,6 +264,7 @@ func (c *Config) Path() string {
return c.path return c.path
} }
<<<<<<< HEAD
// Load reads a YAML configuration file from the given medium and path. // Load reads a YAML configuration file from the given medium and path.
// Returns the parsed data as a map, or an error if the file cannot be read or parsed. // Returns the parsed data as a map, or an error if the file cannot be read or parsed.
// Deprecated: Use Config.LoadFile instead. // Deprecated: Use Config.LoadFile instead.
@ -204,6 +300,107 @@ func Save(m coreio.Medium, path string, data map[string]any) error {
return core.E("config.Save", "failed to write config file: "+path, err) return core.E("config.Save", "failed to write config file: "+path, err)
} }
=======
// getNested retrieves a value from a nested map using dot-notation keys.
func getNested(data map[string]any, key string) (any, bool) {
parts := strings.Split(key, ".")
current := any(data)
for i, part := range parts {
m, ok := current.(map[string]any)
if !ok {
return nil, false
}
val, exists := m[part]
if !exists {
return nil, false
}
if i == len(parts)-1 {
return val, true
}
current = val
}
return nil, false
}
// setNested sets a value in a nested map using dot-notation keys,
// creating intermediate maps as needed.
func setNested(data map[string]any, key string, value any) {
parts := strings.Split(key, ".")
current := data
for i, part := range parts {
if i == len(parts)-1 {
current[part] = value
return
}
next, ok := current[part]
if !ok {
next = make(map[string]any)
current[part] = next
}
m, ok := next.(map[string]any)
if !ok {
m = make(map[string]any)
current[part] = m
}
current = m
}
}
// assign sets the value of out to val, handling type conversions.
func assign(val any, out any) error {
switch ptr := out.(type) {
case *string:
switch v := val.(type) {
case string:
*ptr = v
default:
*ptr = fmt.Sprintf("%v", v)
}
case *int:
switch v := val.(type) {
case int:
*ptr = v
case float64:
*ptr = int(v)
case int64:
*ptr = int(v)
default:
return core.E("config.assign", fmt.Sprintf("cannot assign %T to *int", val), nil)
}
case *bool:
switch v := val.(type) {
case bool:
*ptr = v
default:
return core.E("config.assign", fmt.Sprintf("cannot assign %T to *bool", val), nil)
}
case *float64:
switch v := val.(type) {
case float64:
*ptr = v
case int:
*ptr = float64(v)
case int64:
*ptr = float64(v)
default:
return core.E("config.assign", fmt.Sprintf("cannot assign %T to *float64", val), nil)
}
case *any:
*ptr = val
case *map[string]any:
switch v := val.(type) {
case map[string]any:
*ptr = v
default:
return core.E("config.assign", fmt.Sprintf("cannot assign %T to *map[string]any", val), nil)
}
default:
return core.E("config.assign", fmt.Sprintf("unsupported target type: %T", out), nil)
}
>>>>>>> fix/consolidate-workflows
return nil return nil
} }

View file

@ -225,6 +225,7 @@ func TestSave_Good(t *testing.T) {
assert.NoError(t, readErr) assert.NoError(t, readErr)
assert.Contains(t, content, "key: value") assert.Contains(t, content, "key: value")
} }
<<<<<<< HEAD
func TestConfig_LoadFile_Env(t *testing.T) { func TestConfig_LoadFile_Env(t *testing.T) {
m := io.NewMockMedium() m := io.NewMockMedium()
@ -275,3 +276,5 @@ func TestConfig_Get_EmptyKey(t *testing.T) {
assert.Equal(t, "test", full.App.Name) assert.Equal(t, "test", full.App.Name)
assert.Equal(t, 1, full.Version) assert.Equal(t, 1, full.Version)
} }
=======
>>>>>>> fix/consolidate-workflows

45
pkg/config/loader.go Normal file
View file

@ -0,0 +1,45 @@
package config
import (
"path/filepath"
core "github.com/host-uk/core/pkg/framework/core"
"github.com/host-uk/core/pkg/io"
"gopkg.in/yaml.v3"
)
// Load reads a YAML configuration file from the given medium and path.
// Returns the parsed data as a map, or an error if the file cannot be read or parsed.
func Load(m io.Medium, path string) (map[string]any, error) {
content, err := m.Read(path)
if err != nil {
return nil, core.E("config.Load", "failed to read config file: "+path, err)
}
data := make(map[string]any)
if err := yaml.Unmarshal([]byte(content), &data); err != nil {
return nil, core.E("config.Load", "failed to parse config file: "+path, err)
}
return data, nil
}
// Save writes configuration data to a YAML file at the given path.
// It ensures the parent directory exists before writing.
func Save(m io.Medium, path string, data map[string]any) error {
out, err := yaml.Marshal(data)
if err != nil {
return core.E("config.Save", "failed to marshal config", err)
}
dir := filepath.Dir(path)
if err := m.EnsureDir(dir); err != nil {
return core.E("config.Save", "failed to create config directory: "+dir, err)
}
if err := m.Write(path, string(out)); err != nil {
return core.E("config.Save", "failed to write config file: "+path, err)
}
return nil
}

View file

@ -67,6 +67,7 @@ func (s *Service) Set(key string, v any) error {
return s.config.Set(key, v) return s.config.Set(key, v)
} }
<<<<<<< HEAD
// LoadFile merges a configuration file into the central configuration. // LoadFile merges a configuration file into the central configuration.
func (s *Service) LoadFile(m io.Medium, path string) error { func (s *Service) LoadFile(m io.Medium, path string) error {
if s.config == nil { if s.config == nil {
@ -75,6 +76,8 @@ func (s *Service) LoadFile(m io.Medium, path string) error {
return s.config.LoadFile(m, path) return s.config.LoadFile(m, path)
} }
=======
>>>>>>> fix/consolidate-workflows
// Ensure Service implements core.Config and Startable at compile time. // Ensure Service implements core.Config and Startable at compile time.
var ( var (
_ core.Config = (*Service)(nil) _ core.Config = (*Service)(nil)

View file

@ -436,7 +436,11 @@ func (m *LinuxKitManager) Exec(ctx context.Context, id string, cmd []string) err
// Build SSH command // Build SSH command
sshArgs := []string{ sshArgs := []string{
"-p", fmt.Sprintf("%d", sshPort), "-p", fmt.Sprintf("%d", sshPort),
<<<<<<< HEAD
"-o", "StrictHostKeyChecking=yes", "-o", "StrictHostKeyChecking=yes",
=======
"-o", "StrictHostKeyChecking=accept-new",
>>>>>>> fix/consolidate-workflows
"-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "UserKnownHostsFile=~/.core/known_hosts",
"-o", "LogLevel=ERROR", "-o", "LogLevel=ERROR",
"root@localhost", "root@localhost",

View file

@ -216,7 +216,11 @@ func TestLinuxKitManager_Stop_Bad_NotRunning(t *testing.T) {
statePath := filepath.Join(tmpDir, "containers.json") statePath := filepath.Join(tmpDir, "containers.json")
state, err := LoadState(io.Local, statePath) state, err := LoadState(io.Local, statePath)
require.NoError(t, err) require.NoError(t, err)
<<<<<<< HEAD
manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor()) manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor())
=======
manager := NewLinuxKitManagerWithHypervisor(state, NewMockHypervisor())
>>>>>>> fix/consolidate-workflows
container := &Container{ container := &Container{
ID: "abc12345", ID: "abc12345",
@ -236,7 +240,11 @@ func TestLinuxKitManager_List_Good(t *testing.T) {
statePath := filepath.Join(tmpDir, "containers.json") statePath := filepath.Join(tmpDir, "containers.json")
state, err := LoadState(io.Local, statePath) state, err := LoadState(io.Local, statePath)
require.NoError(t, err) require.NoError(t, err)
<<<<<<< HEAD
manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor()) manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor())
=======
manager := NewLinuxKitManagerWithHypervisor(state, NewMockHypervisor())
>>>>>>> fix/consolidate-workflows
_ = state.Add(&Container{ID: "aaa11111", Status: StatusStopped}) _ = state.Add(&Container{ID: "aaa11111", Status: StatusStopped})
_ = state.Add(&Container{ID: "bbb22222", Status: StatusStopped}) _ = state.Add(&Container{ID: "bbb22222", Status: StatusStopped})
@ -253,7 +261,11 @@ func TestLinuxKitManager_List_Good_VerifiesRunningStatus(t *testing.T) {
statePath := filepath.Join(tmpDir, "containers.json") statePath := filepath.Join(tmpDir, "containers.json")
state, err := LoadState(io.Local, statePath) state, err := LoadState(io.Local, statePath)
require.NoError(t, err) require.NoError(t, err)
<<<<<<< HEAD
manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor()) manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor())
=======
manager := NewLinuxKitManagerWithHypervisor(state, NewMockHypervisor())
>>>>>>> fix/consolidate-workflows
// Add a "running" container with a fake PID that doesn't exist // Add a "running" container with a fake PID that doesn't exist
_ = state.Add(&Container{ _ = state.Add(&Container{

View file

@ -414,8 +414,19 @@ kernel:
err = os.WriteFile(filepath.Join(coreDir, "user-custom.yml"), []byte(templateContent), 0644) err = os.WriteFile(filepath.Join(coreDir, "user-custom.yml"), []byte(templateContent), 0644)
require.NoError(t, err) require.NoError(t, err)
<<<<<<< HEAD
tm := NewTemplateManager(io.Local).WithWorkingDir(tmpDir) tm := NewTemplateManager(io.Local).WithWorkingDir(tmpDir)
templates := tm.ListTemplates() templates := tm.ListTemplates()
=======
// Change to the temp directory
oldWd, err := os.Getwd()
require.NoError(t, err)
err = os.Chdir(tmpDir)
require.NoError(t, err)
defer func() { _ = os.Chdir(oldWd) }()
templates := ListTemplates()
>>>>>>> fix/consolidate-workflows
// Should have at least the builtin templates plus the user template // Should have at least the builtin templates plus the user template
assert.GreaterOrEqual(t, len(templates), 3) assert.GreaterOrEqual(t, len(templates), 3)
@ -449,8 +460,19 @@ services:
err = os.WriteFile(filepath.Join(coreDir, "my-user-template.yml"), []byte(templateContent), 0644) err = os.WriteFile(filepath.Join(coreDir, "my-user-template.yml"), []byte(templateContent), 0644)
require.NoError(t, err) require.NoError(t, err)
<<<<<<< HEAD
tm := NewTemplateManager(io.Local).WithWorkingDir(tmpDir) tm := NewTemplateManager(io.Local).WithWorkingDir(tmpDir)
content, err := tm.GetTemplate("my-user-template") content, err := tm.GetTemplate("my-user-template")
=======
// Change to the temp directory
oldWd, err := os.Getwd()
require.NoError(t, err)
err = os.Chdir(tmpDir)
require.NoError(t, err)
defer func() { _ = os.Chdir(oldWd) }()
content, err := GetTemplate("my-user-template")
>>>>>>> fix/consolidate-workflows
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, content, "kernel:") assert.Contains(t, content, "kernel:")
@ -583,7 +605,21 @@ func TestGetUserTemplatesDir_Good_NoDirectory(t *testing.T) {
tm := NewTemplateManager(io.Local).WithWorkingDir("/tmp/nonexistent-wd").WithHomeDir("/tmp/nonexistent-home") tm := NewTemplateManager(io.Local).WithWorkingDir("/tmp/nonexistent-wd").WithHomeDir("/tmp/nonexistent-home")
dir := tm.getUserTemplatesDir() dir := tm.getUserTemplatesDir()
<<<<<<< HEAD
assert.Empty(t, dir) assert.Empty(t, dir)
=======
// Create a temp directory without .core/linuxkit
tmpDir := t.TempDir()
err = os.Chdir(tmpDir)
require.NoError(t, err)
defer func() { _ = os.Chdir(oldWd) }()
dir := getUserTemplatesDir()
// Should return empty string since no templates dir exists
// (unless home dir has one)
assert.True(t, dir == "" || strings.Contains(dir, "linuxkit"))
>>>>>>> fix/consolidate-workflows
} }
func TestScanUserTemplates_Good_DefaultDescription(t *testing.T) { func TestScanUserTemplates_Good_DefaultDescription(t *testing.T) {

View file

@ -70,7 +70,11 @@ func (d *DevOps) Claude(ctx context.Context, projectDir string, opts ClaudeOptio
// Build SSH command with agent forwarding // Build SSH command with agent forwarding
args := []string{ args := []string{
<<<<<<< HEAD
"-o", "StrictHostKeyChecking=yes", "-o", "StrictHostKeyChecking=yes",
=======
"-o", "StrictHostKeyChecking=accept-new",
>>>>>>> fix/consolidate-workflows
"-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "UserKnownHostsFile=~/.core/known_hosts",
"-o", "LogLevel=ERROR", "-o", "LogLevel=ERROR",
"-A", // SSH agent forwarding "-A", // SSH agent forwarding
@ -132,7 +136,11 @@ func (d *DevOps) CopyGHAuth(ctx context.Context) error {
// Use scp to copy gh config // Use scp to copy gh config
cmd := exec.CommandContext(ctx, "scp", cmd := exec.CommandContext(ctx, "scp",
<<<<<<< HEAD
"-o", "StrictHostKeyChecking=yes", "-o", "StrictHostKeyChecking=yes",
=======
"-o", "StrictHostKeyChecking=accept-new",
>>>>>>> fix/consolidate-workflows
"-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "UserKnownHostsFile=~/.core/known_hosts",
"-o", "LogLevel=ERROR", "-o", "LogLevel=ERROR",
"-P", fmt.Sprintf("%d", DefaultSSHPort), "-P", fmt.Sprintf("%d", DefaultSSHPort),

View file

@ -59,7 +59,11 @@ func (d *DevOps) mountProject(ctx context.Context, path string) error {
// Use reverse SSHFS mount // Use reverse SSHFS mount
// The VM connects back to host to mount the directory // The VM connects back to host to mount the directory
cmd := exec.CommandContext(ctx, "ssh", cmd := exec.CommandContext(ctx, "ssh",
<<<<<<< HEAD
"-o", "StrictHostKeyChecking=yes", "-o", "StrictHostKeyChecking=yes",
=======
"-o", "StrictHostKeyChecking=accept-new",
>>>>>>> fix/consolidate-workflows
"-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "UserKnownHostsFile=~/.core/known_hosts",
"-o", "LogLevel=ERROR", "-o", "LogLevel=ERROR",
"-R", "10000:localhost:22", // Reverse tunnel for SSHFS "-R", "10000:localhost:22", // Reverse tunnel for SSHFS

View file

@ -33,7 +33,11 @@ func (d *DevOps) Shell(ctx context.Context, opts ShellOptions) error {
// sshShell connects via SSH. // sshShell connects via SSH.
func (d *DevOps) sshShell(ctx context.Context, command []string) error { func (d *DevOps) sshShell(ctx context.Context, command []string) error {
args := []string{ args := []string{
<<<<<<< HEAD
"-o", "StrictHostKeyChecking=yes", "-o", "StrictHostKeyChecking=yes",
=======
"-o", "StrictHostKeyChecking=accept-new",
>>>>>>> fix/consolidate-workflows
"-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "UserKnownHostsFile=~/.core/known_hosts",
"-o", "LogLevel=ERROR", "-o", "LogLevel=ERROR",
"-A", // Agent forwarding "-A", // Agent forwarding

View file

@ -131,6 +131,7 @@ func TestFeatures_IsEnabled_Good(t *testing.T) {
assert.False(t, c.Features.IsEnabled("")) assert.False(t, c.Features.IsEnabled(""))
} }
<<<<<<< HEAD
func TestFeatures_IsEnabled_Edge(t *testing.T) { func TestFeatures_IsEnabled_Edge(t *testing.T) {
c, _ := New() c, _ := New()
c.Features.Flags = []string{" ", "foo"} c.Features.Flags = []string{" ", "foo"}
@ -139,6 +140,8 @@ func TestFeatures_IsEnabled_Edge(t *testing.T) {
assert.False(t, c.Features.IsEnabled("FOO")) // Case sensitive check assert.False(t, c.Features.IsEnabled("FOO")) // Case sensitive check
} }
=======
>>>>>>> fix/consolidate-workflows
func TestCore_ServiceLifecycle_Good(t *testing.T) { func TestCore_ServiceLifecycle_Good(t *testing.T) {
c, err := New() c, err := New()
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -33,6 +33,7 @@ func (m *Medium) path(p string) string {
if p == "" { if p == "" {
return m.root return m.root
} }
<<<<<<< HEAD
// If the path is relative and the medium is rooted at "/", // If the path is relative and the medium is rooted at "/",
// treat it as relative to the current working directory. // treat it as relative to the current working directory.
@ -40,6 +41,23 @@ func (m *Medium) path(p string) string {
if m.root == "/" && !filepath.IsAbs(p) { if m.root == "/" && !filepath.IsAbs(p) {
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
return filepath.Join(cwd, p) return filepath.Join(cwd, p)
=======
clean := strings.ReplaceAll(p, "..", ".")
if filepath.IsAbs(clean) {
// If root is "/", allow absolute paths through
if m.root == "/" {
return filepath.Clean(clean)
}
// Otherwise, sandbox absolute paths by stripping volume + leading separators
vol := filepath.VolumeName(clean)
clean = strings.TrimPrefix(clean, vol)
cutset := string(os.PathSeparator)
if os.PathSeparator != '/' {
cutset += "/"
}
clean = strings.TrimLeft(clean, cutset)
return filepath.Join(m.root, clean)
>>>>>>> fix/consolidate-workflows
} }
// Use filepath.Clean with a leading slash to resolve all .. and . internally // Use filepath.Clean with a leading slash to resolve all .. and . internally

View file

@ -41,12 +41,12 @@ func (s *Service) ServeTCP(ctx context.Context, addr string) error {
if err != nil { if err != nil {
return err return err
} }
defer t.listener.Close() defer func() { _ = t.listener.Close() }()
// Close listener when context is cancelled to unblock Accept // Close listener when context is cancelled to unblock Accept
go func() { go func() {
<-ctx.Done() <-ctx.Done()
t.listener.Close() _ = t.listener.Close()
}() }()
if addr == "" { if addr == "" {