diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..1bc44a00 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -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 }}" diff --git a/go.mod b/go.mod index eeacb318..d968f162 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,10 @@ module github.com/host-uk/core go 1.25.5 require ( +<<<<<<< HEAD code.gitea.io/sdk/gitea v0.23.2 +======= +>>>>>>> fix/consolidate-workflows github.com/Snider/Borg v0.2.0 github.com/getkin/kin-openapi v0.133.0 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/Microsoft/go-winio v0.6.2 // 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/adrg/xdg v0.5.3 // 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/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect +<<<<<<< HEAD github.com/fsnotify/fsnotify v1.9.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/go-billy/v5 v5.7.0 // 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/gofrs/flock v0.12.1 // 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/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect @@ -90,6 +99,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-colorable v0.1.14 // 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/ncruces/go-strftime v1.0.0 // 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/sagikazarmark/locafero v0.11.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/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.2 // indirect diff --git a/go.sum b/go.sum index ce854618..12837ea9 100644 --- a/go.sum +++ b/go.sum @@ -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/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= 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/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/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 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/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/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= 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/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/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/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/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/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= 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/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= 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/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= 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/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= 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/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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/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/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= 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/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/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= diff --git a/internal/cmd/dev/cmd_apply.go b/internal/cmd/dev/cmd_apply.go index 4385718f..52d6f33e 100644 --- a/internal/cmd/dev/cmd_apply.go +++ b/internal/cmd/dev/cmd_apply.go @@ -15,7 +15,11 @@ import ( "strings" "github.com/host-uk/core/pkg/cli" +<<<<<<< HEAD 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/i18n" "github.com/host-uk/core/pkg/io" @@ -66,19 +70,19 @@ func runApply() error { // Validate inputs 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 != "" { - 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 == "" { - 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 if 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 { - 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 @@ -227,12 +231,12 @@ func getApplyTargetRepos() ([]*repos.Repo, error) { // Load registry registryPath, err := repos.FindRegistry(io.Local) 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) 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 diff --git a/internal/cmd/dev/cmd_sync.go b/internal/cmd/dev/cmd_sync.go index 33670d02..ef9b7d02 100644 --- a/internal/cmd/dev/cmd_sync.go +++ b/internal/cmd/dev/cmd_sync.go @@ -2,7 +2,6 @@ package dev import ( "bytes" - "context" "go/ast" "go/parser" "go/token" @@ -17,25 +16,6 @@ import ( "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. func addSyncCommand(parent *cli.Command) { syncCmd := &cli.Command{ diff --git a/internal/cmd/dev/service.go b/internal/cmd/dev/service.go index 8c035698..167410c6 100644 --- a/internal/cmd/dev/service.go +++ b/internal/cmd/dev/service.go @@ -174,6 +174,7 @@ func (s *Service) runWork(task TaskWork) error { cli.Print(" %s: %d commits\n", st.Name, st.Ahead) } +<<<<<<< HEAD if !task.AutoPush { cli.Blank() cli.Print("Push all? [y/N] ") @@ -183,6 +184,15 @@ func (s *Service) runWork(task TaskWork) error { cli.Println("Aborted") 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() diff --git a/internal/cmd/docs/cmd_scan.go b/internal/cmd/docs/cmd_scan.go index 08eb2eab..7f4d6b5c 100644 --- a/internal/cmd/docs/cmd_scan.go +++ b/internal/cmd/docs/cmd_scan.go @@ -117,7 +117,7 @@ func scanRepoDocs(repo *repos.Repo) RepoDocInfo { docsDir := filepath.Join(repo.Path, "docs") // Check if directory exists by listing it 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 { return nil } diff --git a/internal/cmd/go/cmd_gotest.go b/internal/cmd/go/cmd_gotest.go index acc8af8b..b7f7532a 100644 --- a/internal/cmd/go/cmd_gotest.go +++ b/internal/cmd/go/cmd_gotest.go @@ -212,6 +212,7 @@ func addGoCovCommand(parent *cli.Command) { } covPath := covFile.Name() _ = covFile.Close() +<<<<<<< HEAD defer func() { if covOutput == "" { _ = os.Remove(covPath) @@ -227,6 +228,9 @@ func addGoCovCommand(parent *cli.Command) { _ = 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")) // Truncate package list if too long for display @@ -269,7 +273,11 @@ func addGoCovCommand(parent *cli.Command) { parts := strings.Fields(lastLine) if len(parts) >= 3 { covStr := strings.TrimSuffix(parts[len(parts)-1], "%") +<<<<<<< HEAD _, _ = fmt.Sscanf(covStr, "%f", &statementCov) +======= + _, _ = fmt.Sscanf(covStr, "%f", &totalCov) +>>>>>>> fix/consolidate-workflows } } } diff --git a/internal/cmd/php/cmd_qa_runner.go b/internal/cmd/php/cmd_qa_runner.go index 69c8a6e4..3f51ff2d 100644 --- a/internal/cmd/php/cmd_qa_runner.go +++ b/internal/cmd/php/cmd_qa_runner.go @@ -150,7 +150,11 @@ func (r *QARunner) buildSpec(check string) *process.RunSpec { phpunitBin := filepath.Join(r.dir, "vendor", "bin", "phpunit") var cmd string +<<<<<<< HEAD if m.IsFile(pestBin) { +======= + if _, err := os.Stat(pestBin); err == nil { +>>>>>>> fix/consolidate-workflows cmd = pestBin } else if m.IsFile(phpunitBin) { cmd = phpunitBin diff --git a/internal/cmd/php/detect.go b/internal/cmd/php/detect.go index c13da9d7..0d62a756 100644 --- a/internal/cmd/php/detect.go +++ b/internal/cmd/php/detect.go @@ -174,6 +174,10 @@ func needsRedis(dir string) bool { if err != nil { return false } +<<<<<<< HEAD +======= + defer func() { _ = file.Close() }() +>>>>>>> fix/consolidate-workflows lines := strings.Split(content, "\n") for _, line := range lines { @@ -238,6 +242,10 @@ func GetLaravelAppName(dir string) string { if err != nil { return "" } +<<<<<<< HEAD +======= + defer func() { _ = file.Close() }() +>>>>>>> fix/consolidate-workflows lines := strings.Split(content, "\n") for _, line := range lines { @@ -261,6 +269,10 @@ func GetLaravelAppURL(dir string) string { if err != nil { return "" } +<<<<<<< HEAD +======= + defer func() { _ = file.Close() }() +>>>>>>> fix/consolidate-workflows lines := strings.Split(content, "\n") for _, line := range lines { diff --git a/internal/variants/full.go b/internal/variants/full.go index 720c4561..c022de21 100644 --- a/internal/variants/full.go +++ b/internal/variants/full.go @@ -27,6 +27,9 @@ import ( // Commands via self-registration _ "github.com/host-uk/core/internal/cmd/ai" _ "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/dev" _ "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/php" _ "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/sdk" _ "github.com/host-uk/core/internal/cmd/security" diff --git a/pkg/agentic/config.go b/pkg/agentic/config.go index c621b086..0ce973d9 100644 --- a/pkg/agentic/config.go +++ b/pkg/agentic/config.go @@ -99,6 +99,10 @@ func loadEnvFile(path string, cfg *Config) error { if err != nil { return err } +<<<<<<< HEAD +======= + defer func() { _ = file.Close() }() +>>>>>>> fix/consolidate-workflows for _, line := range strings.Split(content, "\n") { line = strings.TrimSpace(line) diff --git a/pkg/build/signing/codesign.go b/pkg/build/signing/codesign.go index 11581c7d..16c0ea8e 100644 --- a/pkg/build/signing/codesign.go +++ b/pkg/build/signing/codesign.go @@ -74,7 +74,11 @@ func (s *MacOSSigner) Notarize(ctx context.Context, fs io.Medium, binary string) if output, err := zipCmd.CombinedOutput(); err != nil { return fmt.Errorf("codesign.Notarize: failed to create zip: %w\nOutput: %s", err, string(output)) } +<<<<<<< HEAD defer func() { _ = fs.Delete(zipPath) }() +======= + defer func() { _ = os.Remove(zipPath) }() +>>>>>>> fix/consolidate-workflows // Submit to Apple and wait submitCmd := exec.CommandContext(ctx, "xcrun", "notarytool", "submit", diff --git a/pkg/config/config.go b/pkg/config/config.go index 67ede680..5fdeb65d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -13,11 +13,15 @@ package config import ( "fmt" "os" +<<<<<<< HEAD "path/filepath" +======= +>>>>>>> fix/consolidate-workflows "strings" "sync" core "github.com/host-uk/core/pkg/framework/core" +<<<<<<< HEAD coreio "github.com/host-uk/core/pkg/io" "github.com/spf13/viper" "gopkg.in/yaml.v3" @@ -30,13 +34,29 @@ type Config struct { v *viper.Viper medium coreio.Medium 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. type Option func(*Config) // WithMedium sets the storage medium for configuration file operations. +<<<<<<< HEAD func WithMedium(m coreio.Medium) Option { +======= +func WithMedium(m io.Medium) Option { +>>>>>>> fix/consolidate-workflows return func(c *Config) { c.medium = m } @@ -49,6 +69,7 @@ func WithPath(path string) Option { } } +<<<<<<< HEAD // WithEnvPrefix sets the prefix for environment variables. func WithEnvPrefix(prefix string) Option { 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. // If no medium is provided, it defaults to io.Local. // If no path is provided, it defaults to ~/.core/config.yaml. func New(opts ...Option) (*Config, error) { c := &Config{ +<<<<<<< HEAD v: viper.New(), } @@ -68,12 +92,21 @@ func New(opts ...Option) (*Config, error) { c.v.SetEnvPrefix("CORE_CONFIG") c.v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) +======= + data: make(map[string]any), + } + +>>>>>>> fix/consolidate-workflows for _, opt := range opts { opt(c) } if c.medium == nil { +<<<<<<< HEAD c.medium = coreio.Local +======= + c.medium = io.Local +>>>>>>> fix/consolidate-workflows } if c.path == "" { @@ -81,6 +114,7 @@ func New(opts ...Option) (*Config, error) { if err != nil { return nil, core.E("config.New", "failed to determine home directory", err) } +<<<<<<< HEAD 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 { 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 } +<<<<<<< HEAD // 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). 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. // If key is empty, it unmarshals the entire configuration into out. // 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 { c.mu.RLock() defer c.mu.RUnlock() +<<<<<<< HEAD if key == "" { return c.v.Unmarshal(out) } @@ -139,6 +198,14 @@ func (c *Config) Get(key string, out any) error { } 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. @@ -146,10 +213,16 @@ func (c *Config) Set(key string, v any) error { c.mu.Lock() defer c.mu.Unlock() +<<<<<<< HEAD c.v.Set(key, v) // Persist to disk 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) } @@ -161,7 +234,29 @@ func (c *Config) All() map[string]any { c.mu.RLock() defer c.mu.RUnlock() +<<<<<<< HEAD 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. @@ -169,6 +264,7 @@ func (c *Config) Path() string { return c.path } +<<<<<<< HEAD // 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. // 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) } +======= +// 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 } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index daa9f49f..762f8405 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -225,6 +225,7 @@ func TestSave_Good(t *testing.T) { assert.NoError(t, readErr) assert.Contains(t, content, "key: value") } +<<<<<<< HEAD func TestConfig_LoadFile_Env(t *testing.T) { m := io.NewMockMedium() @@ -275,3 +276,5 @@ func TestConfig_Get_EmptyKey(t *testing.T) { assert.Equal(t, "test", full.App.Name) assert.Equal(t, 1, full.Version) } +======= +>>>>>>> fix/consolidate-workflows diff --git a/pkg/config/loader.go b/pkg/config/loader.go new file mode 100644 index 00000000..628abfcb --- /dev/null +++ b/pkg/config/loader.go @@ -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 +} diff --git a/pkg/config/service.go b/pkg/config/service.go index ebdf4358..b579442e 100644 --- a/pkg/config/service.go +++ b/pkg/config/service.go @@ -67,6 +67,7 @@ func (s *Service) Set(key string, v any) error { return s.config.Set(key, v) } +<<<<<<< HEAD // LoadFile merges a configuration file into the central configuration. func (s *Service) LoadFile(m io.Medium, path string) error { if s.config == nil { @@ -75,6 +76,8 @@ func (s *Service) LoadFile(m io.Medium, path string) error { return s.config.LoadFile(m, path) } +======= +>>>>>>> fix/consolidate-workflows // Ensure Service implements core.Config and Startable at compile time. var ( _ core.Config = (*Service)(nil) diff --git a/pkg/container/linuxkit.go b/pkg/container/linuxkit.go index e771b33c..06647a63 100644 --- a/pkg/container/linuxkit.go +++ b/pkg/container/linuxkit.go @@ -436,7 +436,11 @@ func (m *LinuxKitManager) Exec(ctx context.Context, id string, cmd []string) err // Build SSH command sshArgs := []string{ "-p", fmt.Sprintf("%d", sshPort), +<<<<<<< HEAD "-o", "StrictHostKeyChecking=yes", +======= + "-o", "StrictHostKeyChecking=accept-new", +>>>>>>> fix/consolidate-workflows "-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "LogLevel=ERROR", "root@localhost", diff --git a/pkg/container/linuxkit_test.go b/pkg/container/linuxkit_test.go index b943898a..06c13593 100644 --- a/pkg/container/linuxkit_test.go +++ b/pkg/container/linuxkit_test.go @@ -216,7 +216,11 @@ func TestLinuxKitManager_Stop_Bad_NotRunning(t *testing.T) { statePath := filepath.Join(tmpDir, "containers.json") state, err := LoadState(io.Local, statePath) require.NoError(t, err) +<<<<<<< HEAD manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor()) +======= + manager := NewLinuxKitManagerWithHypervisor(state, NewMockHypervisor()) +>>>>>>> fix/consolidate-workflows container := &Container{ ID: "abc12345", @@ -236,7 +240,11 @@ func TestLinuxKitManager_List_Good(t *testing.T) { statePath := filepath.Join(tmpDir, "containers.json") state, err := LoadState(io.Local, statePath) require.NoError(t, err) +<<<<<<< HEAD manager := NewLinuxKitManagerWithHypervisor(io.Local, state, NewMockHypervisor()) +======= + manager := NewLinuxKitManagerWithHypervisor(state, NewMockHypervisor()) +>>>>>>> fix/consolidate-workflows _ = state.Add(&Container{ID: "aaa11111", 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") state, err := LoadState(io.Local, statePath) require.NoError(t, err) +<<<<<<< HEAD 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 _ = state.Add(&Container{ diff --git a/pkg/container/templates_test.go b/pkg/container/templates_test.go index c1db5a4e..df4ae739 100644 --- a/pkg/container/templates_test.go +++ b/pkg/container/templates_test.go @@ -414,8 +414,19 @@ kernel: err = os.WriteFile(filepath.Join(coreDir, "user-custom.yml"), []byte(templateContent), 0644) require.NoError(t, err) +<<<<<<< HEAD tm := NewTemplateManager(io.Local).WithWorkingDir(tmpDir) 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 assert.GreaterOrEqual(t, len(templates), 3) @@ -449,8 +460,19 @@ services: err = os.WriteFile(filepath.Join(coreDir, "my-user-template.yml"), []byte(templateContent), 0644) require.NoError(t, err) +<<<<<<< HEAD tm := NewTemplateManager(io.Local).WithWorkingDir(tmpDir) 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) 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") dir := tm.getUserTemplatesDir() +<<<<<<< HEAD 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) { diff --git a/pkg/devops/claude.go b/pkg/devops/claude.go index 7bfef0b3..99a35a10 100644 --- a/pkg/devops/claude.go +++ b/pkg/devops/claude.go @@ -70,7 +70,11 @@ func (d *DevOps) Claude(ctx context.Context, projectDir string, opts ClaudeOptio // Build SSH command with agent forwarding args := []string{ +<<<<<<< HEAD "-o", "StrictHostKeyChecking=yes", +======= + "-o", "StrictHostKeyChecking=accept-new", +>>>>>>> fix/consolidate-workflows "-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "LogLevel=ERROR", "-A", // SSH agent forwarding @@ -132,7 +136,11 @@ func (d *DevOps) CopyGHAuth(ctx context.Context) error { // Use scp to copy gh config cmd := exec.CommandContext(ctx, "scp", +<<<<<<< HEAD "-o", "StrictHostKeyChecking=yes", +======= + "-o", "StrictHostKeyChecking=accept-new", +>>>>>>> fix/consolidate-workflows "-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "LogLevel=ERROR", "-P", fmt.Sprintf("%d", DefaultSSHPort), diff --git a/pkg/devops/serve.go b/pkg/devops/serve.go index aac0e8ad..3803583e 100644 --- a/pkg/devops/serve.go +++ b/pkg/devops/serve.go @@ -59,7 +59,11 @@ func (d *DevOps) mountProject(ctx context.Context, path string) error { // Use reverse SSHFS mount // The VM connects back to host to mount the directory cmd := exec.CommandContext(ctx, "ssh", +<<<<<<< HEAD "-o", "StrictHostKeyChecking=yes", +======= + "-o", "StrictHostKeyChecking=accept-new", +>>>>>>> fix/consolidate-workflows "-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "LogLevel=ERROR", "-R", "10000:localhost:22", // Reverse tunnel for SSHFS diff --git a/pkg/devops/shell.go b/pkg/devops/shell.go index fe94d1bd..3afdbcb6 100644 --- a/pkg/devops/shell.go +++ b/pkg/devops/shell.go @@ -33,7 +33,11 @@ func (d *DevOps) Shell(ctx context.Context, opts ShellOptions) error { // sshShell connects via SSH. func (d *DevOps) sshShell(ctx context.Context, command []string) error { args := []string{ +<<<<<<< HEAD "-o", "StrictHostKeyChecking=yes", +======= + "-o", "StrictHostKeyChecking=accept-new", +>>>>>>> fix/consolidate-workflows "-o", "UserKnownHostsFile=~/.core/known_hosts", "-o", "LogLevel=ERROR", "-A", // Agent forwarding diff --git a/pkg/framework/core/core_test.go b/pkg/framework/core/core_test.go index 07c43cfa..e5550df9 100644 --- a/pkg/framework/core/core_test.go +++ b/pkg/framework/core/core_test.go @@ -131,6 +131,7 @@ func TestFeatures_IsEnabled_Good(t *testing.T) { assert.False(t, c.Features.IsEnabled("")) } +<<<<<<< HEAD func TestFeatures_IsEnabled_Edge(t *testing.T) { c, _ := New() 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 } +======= +>>>>>>> fix/consolidate-workflows func TestCore_ServiceLifecycle_Good(t *testing.T) { c, err := New() assert.NoError(t, err) diff --git a/pkg/io/local/client.go b/pkg/io/local/client.go index 78310e4e..7bbcae05 100644 --- a/pkg/io/local/client.go +++ b/pkg/io/local/client.go @@ -33,6 +33,7 @@ func (m *Medium) path(p string) string { if p == "" { return m.root } +<<<<<<< HEAD // If the path is relative and the medium is rooted at "/", // 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) { cwd, _ := os.Getwd() 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 diff --git a/pkg/mcp/transport_tcp.go b/pkg/mcp/transport_tcp.go index ba37bfb8..492ef5ed 100644 --- a/pkg/mcp/transport_tcp.go +++ b/pkg/mcp/transport_tcp.go @@ -41,12 +41,12 @@ func (s *Service) ServeTCP(ctx context.Context, addr string) error { if err != nil { return err } - defer t.listener.Close() + defer func() { _ = t.listener.Close() }() // Close listener when context is cancelled to unblock Accept go func() { <-ctx.Done() - t.listener.Close() + _ = t.listener.Close() }() if addr == "" {