From 073db250666e9a3d1e304ee487253fbc16ca3afc Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:35:34 +0700 Subject: [PATCH 01/20] refactor: move the Go module to apps/cli Make room for more apps and shared packages next to the CLI. The module path becomes github.com/PunGrumpy/plumb/apps/cli so go install resolves it from its new directory. --- .github/workflows/ci.yml | 8 +-- .github/workflows/release.yml | 2 +- .goreleaser.yaml | 3 +- README.md | 6 +-- Makefile => apps/cli/Makefile | 0 {cmd => apps/cli/cmd}/plumb/account.go | 8 +-- {cmd => apps/cli/cmd}/plumb/commands.go | 12 ++--- {cmd => apps/cli/cmd}/plumb/main.go | 6 +-- {cmd => apps/cli/cmd}/plumb/main_test.go | 10 ++-- .../cli/cmd}/plumb/testdata/demo.golden | 0 {cmd => apps/cli/cmd}/plumb/update.go | 4 +- apps/cli/go.mod | 3 ++ {internal => apps/cli/internal}/demo/demo.go | 0 ...11_8083_Snh_ShowBgpNeighborSummaryReq.http | 0 ...e_default-domain_admin_vn1_vn1.inet.0.http | 0 ...12_8083_Snh_ShowBgpNeighborSummaryReq.http | 0 ...e_default-domain_admin_vn1_vn1.inet.0.http | 0 ...8085_Snh_AgentXmppConnectionStatusReq.http | 0 ....10.0.21_8085_Snh_FetchAllFlowRecords.http | 0 ...ix_len_32_src_ip_10.0.1.5_vrf_index_3.http | 0 ..._9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http | 0 ...Req_name_default-domain_admin_vn1_vn1.http | 0 ...nfig.lab_8082_bgp-routers_detail_true.http | 0 ..._f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9.http | 0 ..._c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f.http | 0 ..._8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a.http | 0 ..._9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http | 0 ..._3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http | 0 ..._2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http | 0 ..._5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9.http | 0 ...atingips_floating_ip_address_10.0.1.1.http | 0 ...gips_floating_ip_address_203.0.113.10.http | 0 ..._9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http | 0 ..._2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http | 0 ..._9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http | 0 ..._3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http | 0 ...vice_owner_network_h_b612f0d0f1df5967.http | 0 ...0_ports_fixed_ips_ip_address_10.0.1.1.http | 0 ...0_ports_fixed_ips_ip_address_10.0.1.5.http | 0 ...rts_fixed_ips_ip_address_203.0.113.10.http | 0 ..._6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d.http | 0 ..._4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c7d.http | 0 ..._7e3a1b2c-9d4f-4e5a-8b6c-1d2e3f4a5b6c.http | 0 ..._3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http | 0 ...d-4e2a-9c61-0d4b8e7f1a23_os-interface.http | 0 ....lab_compute_v2.1_servers_name_web-01.http | 0 ..._6b0e1f2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b.http | 0 ...POST_demo.lab_identity_v3_auth_tokens.http | 0 .../cli/internal}/doctor/doctor.go | 10 ++-- .../cli/internal}/doctor/doctor_test.go | 8 +-- .../cli/internal}/glance/glance.go | 2 +- .../cli/internal}/httpx/client.go | 0 .../cli/internal}/httpx/record.go | 0 .../cli/internal}/httpx/record_test.go | 0 .../cli/internal}/keystone/keystone.go | 2 +- .../cli/internal}/keystone/keystone_test.go | 0 .../cli/internal}/neutron/neutron.go | 2 +- {internal => apps/cli/internal}/nova/nova.go | 2 +- .../cli/internal}/opensdn/agent/agent.go | 4 +- .../cli/internal}/opensdn/config/config.go | 2 +- .../cli/internal}/opensdn/control/control.go | 4 +- .../cli/internal}/opensdn/sandesh/sandesh.go | 2 +- .../internal}/opensdn/sandesh/sandesh_test.go | 0 .../cli/internal}/profile/profile.go | 0 .../cli/internal}/render/tree.go | 4 +- .../cli/internal}/trace/issues.go | 2 +- .../cli/internal}/trace/model.go | 0 .../cli/internal}/trace/opensdn.go | 8 +-- .../cli/internal}/trace/openstack.go | 10 ++-- {internal => apps/cli/internal}/trace/path.go | 4 +- .../cli/internal}/trace/path_test.go | 2 +- .../cli/internal}/trace/trace.go | 4 +- {internal => apps/cli/internal}/ui/ui.go | 0 .../cli/internal}/update/update.go | 0 .../cli/internal}/update/update_test.go | 0 docs/add-a-stage.md | 18 +++---- docs/api-reference.md | 2 +- docs/architecture.md | 49 ++++++++++--------- docs/cli-reference.md | 6 +-- docs/quickstart.md | 14 +++--- docs/releasing.md | 2 +- docs/run-against-a-lab.md | 10 ++-- go.mod | 3 -- 83 files changed, 121 insertions(+), 117 deletions(-) rename Makefile => apps/cli/Makefile (100%) rename {cmd => apps/cli/cmd}/plumb/account.go (95%) rename {cmd => apps/cli/cmd}/plumb/commands.go (96%) rename {cmd => apps/cli/cmd}/plumb/main.go (98%) rename {cmd => apps/cli/cmd}/plumb/main_test.go (97%) rename {cmd => apps/cli/cmd}/plumb/testdata/demo.golden (100%) rename {cmd => apps/cli/cmd}/plumb/update.go (94%) create mode 100644 apps/cli/go.mod rename {internal => apps/cli/internal}/demo/demo.go (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.11_8083_Snh_ShowBgpNeighborSummaryReq.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.11_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.12_8083_Snh_ShowBgpNeighborSummaryReq.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.12_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.21_8085_Snh_AgentXmppConnectionStatusReq.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.21_8085_Snh_FetchAllFlowRecords.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.21_8085_Snh_Inet4UcRouteReq_prefix_len_32_src_ip_10.0.1.5_vrf_index_3.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.21_8085_Snh_ItfReq_uuid_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_10.10.0.21_8085_Snh_VrfListReq_name_default-domain_admin_vn1_vn1.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_bgp-routers_detail_true.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_floating-ip_f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_instance-ip_c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_routing-instance_8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_virtual-machine-interface_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_virtual-machine_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_virtual-network_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_config.lab_8082_virtual-router_5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_10.0.1.1.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_203.0.113.10.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_port_id_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_networks_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_id_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_owner_network_router_interface_device_owner_network_router_interface_distributed_device_owner_network_h_b612f0d0f1df5967.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.1.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.5.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_203.0.113.10.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_routers_6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_security-groups_4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c7d.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_9696_networking_v2.0_subnets_7e3a1b2c-9d4f-4e5a-8b6c-1d2e3f4a5b6c.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23_os-interface.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_compute_v2.1_servers_name_web-01.http (100%) rename {internal => apps/cli/internal}/demo/lab/GET_demo.lab_image_v2_images_6b0e1f2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b.http (100%) rename {internal => apps/cli/internal}/demo/lab/POST_demo.lab_identity_v3_auth_tokens.http (100%) rename {internal => apps/cli/internal}/doctor/doctor.go (96%) rename {internal => apps/cli/internal}/doctor/doctor_test.go (93%) rename {internal => apps/cli/internal}/glance/glance.go (92%) rename {internal => apps/cli/internal}/httpx/client.go (100%) rename {internal => apps/cli/internal}/httpx/record.go (100%) rename {internal => apps/cli/internal}/httpx/record_test.go (100%) rename {internal => apps/cli/internal}/keystone/keystone.go (99%) rename {internal => apps/cli/internal}/keystone/keystone_test.go (100%) rename {internal => apps/cli/internal}/neutron/neutron.go (99%) rename {internal => apps/cli/internal}/nova/nova.go (98%) rename {internal => apps/cli/internal}/opensdn/agent/agent.go (97%) rename {internal => apps/cli/internal}/opensdn/config/config.go (99%) rename {internal => apps/cli/internal}/opensdn/control/control.go (95%) rename {internal => apps/cli/internal}/opensdn/sandesh/sandesh.go (98%) rename {internal => apps/cli/internal}/opensdn/sandesh/sandesh_test.go (100%) rename {internal => apps/cli/internal}/profile/profile.go (100%) rename {internal => apps/cli/internal}/render/tree.go (99%) rename {internal => apps/cli/internal}/trace/issues.go (99%) rename {internal => apps/cli/internal}/trace/model.go (100%) rename {internal => apps/cli/internal}/trace/opensdn.go (98%) rename {internal => apps/cli/internal}/trace/openstack.go (97%) rename {internal => apps/cli/internal}/trace/path.go (99%) rename {internal => apps/cli/internal}/trace/path_test.go (99%) rename {internal => apps/cli/internal}/trace/trace.go (97%) rename {internal => apps/cli/internal}/ui/ui.go (100%) rename {internal => apps/cli/internal}/update/update.go (100%) rename {internal => apps/cli/internal}/update/update_test.go (100%) delete mode 100644 go.mod diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 182ecce..a75ee89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,15 +15,17 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version-file: apps/cli/go.mod - name: Check formatting run: | - unformatted=$(gofmt -l .) + unformatted=$(gofmt -l apps/cli) if [ -n "$unformatted" ]; then echo "Run gofmt -w on:" && echo "$unformatted" exit 1 fi - run: go vet ./... + working-directory: apps/cli - run: go test -race ./... + working-directory: apps/cli - name: Cross-compile every release platform - run: make dist + run: make -C apps/cli dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39c4fdb..deb325a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: - if: steps.tag.outputs.created == 'true' uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version-file: apps/cli/go.mod - if: steps.tag.outputs.created == 'true' uses: goreleaser/goreleaser-action@v6 with: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 6cb7a44..6d5a91b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,7 +4,8 @@ version: 2 project_name: plumb builds: - - main: ./cmd/plumb + - dir: apps/cli + main: ./cmd/plumb binary: plumb env: [CGO_ENABLED=0] goos: [linux, darwin] diff --git a/README.md b/README.md index b8bb151..d948f43 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ This is the port section of a trace against the built-in lab, with long lines cu Build plumb with Go 1.24 or later, then trace a VM in the lab that ships inside the binary. You don't need a cloud or credentials for this step: ```sh -make build -./bin/plumb demo +make -C apps/cli build +./apps/cli/bin/plumb demo ``` On a real cloud, source your openrc and use the commands in this order: @@ -61,7 +61,7 @@ On a real cloud, source your openrc and use the commands in this order: | `plumb explain ` | A trace ends with a `More` line and you want the details | | `plumb whoami` | You want to see the user, project and OpenSDN URLs plumb uses | -To install plumb on a server, run `make dist` and copy the static binary for its platform from `dist/`. +To install plumb on a server, run `make -C apps/cli dist` and copy the static binary for its platform from `apps/cli/dist/`. ## Documentation diff --git a/Makefile b/apps/cli/Makefile similarity index 100% rename from Makefile rename to apps/cli/Makefile diff --git a/cmd/plumb/account.go b/apps/cli/cmd/plumb/account.go similarity index 95% rename from cmd/plumb/account.go rename to apps/cli/cmd/plumb/account.go index 31c0041..e70281d 100644 --- a/cmd/plumb/account.go +++ b/apps/cli/cmd/plumb/account.go @@ -7,10 +7,10 @@ import ( "strings" "time" - "plumb/internal/doctor" - "plumb/internal/keystone" - "plumb/internal/profile" - "plumb/internal/trace" + "github.com/PunGrumpy/plumb/apps/cli/internal/doctor" + "github.com/PunGrumpy/plumb/apps/cli/internal/keystone" + "github.com/PunGrumpy/plumb/apps/cli/internal/profile" + "github.com/PunGrumpy/plumb/apps/cli/internal/trace" ) func linkCmd(args []string, e env) int { diff --git a/cmd/plumb/commands.go b/apps/cli/cmd/plumb/commands.go similarity index 96% rename from cmd/plumb/commands.go rename to apps/cli/cmd/plumb/commands.go index 7e9bb83..678e240 100644 --- a/cmd/plumb/commands.go +++ b/apps/cli/cmd/plumb/commands.go @@ -8,12 +8,12 @@ import ( "net/http" "time" - "plumb/internal/demo" - "plumb/internal/doctor" - "plumb/internal/httpx" - "plumb/internal/keystone" - "plumb/internal/render" - "plumb/internal/trace" + "github.com/PunGrumpy/plumb/apps/cli/internal/demo" + "github.com/PunGrumpy/plumb/apps/cli/internal/doctor" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/keystone" + "github.com/PunGrumpy/plumb/apps/cli/internal/render" + "github.com/PunGrumpy/plumb/apps/cli/internal/trace" ) var commands = []string{"trace", "path", "doctor", "link", "whoami", "demo", "explain", "version", "help"} diff --git a/cmd/plumb/main.go b/apps/cli/cmd/plumb/main.go similarity index 98% rename from cmd/plumb/main.go rename to apps/cli/cmd/plumb/main.go index 77791b8..4d93b9a 100644 --- a/cmd/plumb/main.go +++ b/apps/cli/cmd/plumb/main.go @@ -13,9 +13,9 @@ import ( "strings" "time" - "plumb/internal/httpx" - "plumb/internal/profile" - "plumb/internal/ui" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/profile" + "github.com/PunGrumpy/plumb/apps/cli/internal/ui" ) // Set at build time with -ldflags "-X main.version=v1.2.3 ...". An empty diff --git a/cmd/plumb/main_test.go b/apps/cli/cmd/plumb/main_test.go similarity index 97% rename from cmd/plumb/main_test.go rename to apps/cli/cmd/plumb/main_test.go index 43c0dcc..bd2a242 100644 --- a/cmd/plumb/main_test.go +++ b/apps/cli/cmd/plumb/main_test.go @@ -11,10 +11,10 @@ import ( "strings" "testing" - "plumb/internal/demo" - "plumb/internal/trace" - "plumb/internal/ui" - "plumb/internal/update" + "github.com/PunGrumpy/plumb/apps/cli/internal/demo" + "github.com/PunGrumpy/plumb/apps/cli/internal/trace" + "github.com/PunGrumpy/plumb/apps/cli/internal/ui" + "github.com/PunGrumpy/plumb/apps/cli/internal/update" ) var updateGolden = flag.Bool("update", false, "rewrite golden files") @@ -182,7 +182,7 @@ func TestHelpAndVersion(t *testing.T) { // Every code `plumb explain` knows must have a heading in the // troubleshooting page, because explain links to it. func TestEveryCodeIsDocumented(t *testing.T) { - doc, err := os.ReadFile("../../docs/troubleshooting.md") + doc, err := os.ReadFile("../../../../docs/troubleshooting.md") if err != nil { t.Fatal(err) } diff --git a/cmd/plumb/testdata/demo.golden b/apps/cli/cmd/plumb/testdata/demo.golden similarity index 100% rename from cmd/plumb/testdata/demo.golden rename to apps/cli/cmd/plumb/testdata/demo.golden diff --git a/cmd/plumb/update.go b/apps/cli/cmd/plumb/update.go similarity index 94% rename from cmd/plumb/update.go rename to apps/cli/cmd/plumb/update.go index 61f424d..d20a390 100644 --- a/cmd/plumb/update.go +++ b/apps/cli/cmd/plumb/update.go @@ -7,8 +7,8 @@ import ( "slices" "time" - "plumb/internal/ui" - "plumb/internal/update" + "github.com/PunGrumpy/plumb/apps/cli/internal/ui" + "github.com/PunGrumpy/plumb/apps/cli/internal/update" ) // checkForUpdate starts the release check in the background and returns a diff --git a/apps/cli/go.mod b/apps/cli/go.mod new file mode 100644 index 0000000..2164ac7 --- /dev/null +++ b/apps/cli/go.mod @@ -0,0 +1,3 @@ +module github.com/PunGrumpy/plumb/apps/cli + +go 1.24 diff --git a/internal/demo/demo.go b/apps/cli/internal/demo/demo.go similarity index 100% rename from internal/demo/demo.go rename to apps/cli/internal/demo/demo.go diff --git a/internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowBgpNeighborSummaryReq.http b/apps/cli/internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowBgpNeighborSummaryReq.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowBgpNeighborSummaryReq.http rename to apps/cli/internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowBgpNeighborSummaryReq.http diff --git a/internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http b/apps/cli/internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http rename to apps/cli/internal/demo/lab/GET_10.10.0.11_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http diff --git a/internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowBgpNeighborSummaryReq.http b/apps/cli/internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowBgpNeighborSummaryReq.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowBgpNeighborSummaryReq.http rename to apps/cli/internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowBgpNeighborSummaryReq.http diff --git a/internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http b/apps/cli/internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http rename to apps/cli/internal/demo/lab/GET_10.10.0.12_8083_Snh_ShowRouteReq_prefix_10.0.1.5_32_routing_table_default-domain_admin_vn1_vn1.inet.0.http diff --git a/internal/demo/lab/GET_10.10.0.21_8085_Snh_AgentXmppConnectionStatusReq.http b/apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_AgentXmppConnectionStatusReq.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.21_8085_Snh_AgentXmppConnectionStatusReq.http rename to apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_AgentXmppConnectionStatusReq.http diff --git a/internal/demo/lab/GET_10.10.0.21_8085_Snh_FetchAllFlowRecords.http b/apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_FetchAllFlowRecords.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.21_8085_Snh_FetchAllFlowRecords.http rename to apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_FetchAllFlowRecords.http diff --git a/internal/demo/lab/GET_10.10.0.21_8085_Snh_Inet4UcRouteReq_prefix_len_32_src_ip_10.0.1.5_vrf_index_3.http b/apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_Inet4UcRouteReq_prefix_len_32_src_ip_10.0.1.5_vrf_index_3.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.21_8085_Snh_Inet4UcRouteReq_prefix_len_32_src_ip_10.0.1.5_vrf_index_3.http rename to apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_Inet4UcRouteReq_prefix_len_32_src_ip_10.0.1.5_vrf_index_3.http diff --git a/internal/demo/lab/GET_10.10.0.21_8085_Snh_ItfReq_uuid_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http b/apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_ItfReq_uuid_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.21_8085_Snh_ItfReq_uuid_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http rename to apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_ItfReq_uuid_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http diff --git a/internal/demo/lab/GET_10.10.0.21_8085_Snh_VrfListReq_name_default-domain_admin_vn1_vn1.http b/apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_VrfListReq_name_default-domain_admin_vn1_vn1.http similarity index 100% rename from internal/demo/lab/GET_10.10.0.21_8085_Snh_VrfListReq_name_default-domain_admin_vn1_vn1.http rename to apps/cli/internal/demo/lab/GET_10.10.0.21_8085_Snh_VrfListReq_name_default-domain_admin_vn1_vn1.http diff --git a/internal/demo/lab/GET_config.lab_8082_bgp-routers_detail_true.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_bgp-routers_detail_true.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_bgp-routers_detail_true.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_bgp-routers_detail_true.http diff --git a/internal/demo/lab/GET_config.lab_8082_floating-ip_f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_floating-ip_f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_floating-ip_f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_floating-ip_f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9.http diff --git a/internal/demo/lab/GET_config.lab_8082_instance-ip_c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_instance-ip_c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_instance-ip_c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_instance-ip_c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f.http diff --git a/internal/demo/lab/GET_config.lab_8082_routing-instance_8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_routing-instance_8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_routing-instance_8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_routing-instance_8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a.http diff --git a/internal/demo/lab/GET_config.lab_8082_virtual-machine-interface_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-machine-interface_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_virtual-machine-interface_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-machine-interface_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http diff --git a/internal/demo/lab/GET_config.lab_8082_virtual-machine_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-machine_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_virtual-machine_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-machine_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http diff --git a/internal/demo/lab/GET_config.lab_8082_virtual-network_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-network_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_virtual-network_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-network_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http diff --git a/internal/demo/lab/GET_config.lab_8082_virtual-router_5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9.http b/apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-router_5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9.http similarity index 100% rename from internal/demo/lab/GET_config.lab_8082_virtual-router_5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9.http rename to apps/cli/internal/demo/lab/GET_config.lab_8082_virtual-router_5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_10.0.1.1.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_10.0.1.1.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_10.0.1.1.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_10.0.1.1.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_203.0.113.10.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_203.0.113.10.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_203.0.113.10.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_floating_ip_address_203.0.113.10.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_port_id_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_port_id_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_port_id_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_floatingips_port_id_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_networks_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_networks_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_networks_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_networks_2b7e9f10-4c3a-4d8b-b6e1-8a9c0d1e2f34.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_9c1e4d2b-7a3f-4b8e-a1d5-6e2f0c9b8a74.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_id_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_id_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_id_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_id_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_owner_network_router_interface_device_owner_network_router_interface_distributed_device_owner_network_h_b612f0d0f1df5967.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_owner_network_router_interface_device_owner_network_router_interface_distributed_device_owner_network_h_b612f0d0f1df5967.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_owner_network_router_interface_device_owner_network_router_interface_distributed_device_owner_network_h_b612f0d0f1df5967.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_device_owner_network_router_interface_device_owner_network_router_interface_distributed_device_owner_network_h_b612f0d0f1df5967.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.1.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.1.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.1.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.1.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.5.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.5.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.5.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_10.0.1.5.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_203.0.113.10.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_203.0.113.10.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_203.0.113.10.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_ports_fixed_ips_ip_address_203.0.113.10.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_routers_6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_routers_6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_routers_6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_routers_6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_security-groups_4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c7d.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_security-groups_4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c7d.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_security-groups_4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c7d.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_security-groups_4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c7d.http diff --git a/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_subnets_7e3a1b2c-9d4f-4e5a-8b6c-1d2e3f4a5b6c.http b/apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_subnets_7e3a1b2c-9d4f-4e5a-8b6c-1d2e3f4a5b6c.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_9696_networking_v2.0_subnets_7e3a1b2c-9d4f-4e5a-8b6c-1d2e3f4a5b6c.http rename to apps/cli/internal/demo/lab/GET_demo.lab_9696_networking_v2.0_subnets_7e3a1b2c-9d4f-4e5a-8b6c-1d2e3f4a5b6c.http diff --git a/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http b/apps/cli/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http rename to apps/cli/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23.http diff --git a/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23_os-interface.http b/apps/cli/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23_os-interface.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23_os-interface.http rename to apps/cli/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_3f2a8c1e-5b7d-4e2a-9c61-0d4b8e7f1a23_os-interface.http diff --git a/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_name_web-01.http b/apps/cli/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_name_web-01.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_compute_v2.1_servers_name_web-01.http rename to apps/cli/internal/demo/lab/GET_demo.lab_compute_v2.1_servers_name_web-01.http diff --git a/internal/demo/lab/GET_demo.lab_image_v2_images_6b0e1f2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b.http b/apps/cli/internal/demo/lab/GET_demo.lab_image_v2_images_6b0e1f2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b.http similarity index 100% rename from internal/demo/lab/GET_demo.lab_image_v2_images_6b0e1f2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b.http rename to apps/cli/internal/demo/lab/GET_demo.lab_image_v2_images_6b0e1f2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b.http diff --git a/internal/demo/lab/POST_demo.lab_identity_v3_auth_tokens.http b/apps/cli/internal/demo/lab/POST_demo.lab_identity_v3_auth_tokens.http similarity index 100% rename from internal/demo/lab/POST_demo.lab_identity_v3_auth_tokens.http rename to apps/cli/internal/demo/lab/POST_demo.lab_identity_v3_auth_tokens.http diff --git a/internal/doctor/doctor.go b/apps/cli/internal/doctor/doctor.go similarity index 96% rename from internal/doctor/doctor.go rename to apps/cli/internal/doctor/doctor.go index 8ab26ae..011de17 100644 --- a/internal/doctor/doctor.go +++ b/apps/cli/internal/doctor/doctor.go @@ -13,11 +13,11 @@ import ( "sync" "time" - "plumb/internal/httpx" - "plumb/internal/keystone" - "plumb/internal/opensdn/config" - "plumb/internal/trace" - "plumb/internal/ui" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/keystone" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/config" + "github.com/PunGrumpy/plumb/apps/cli/internal/trace" + "github.com/PunGrumpy/plumb/apps/cli/internal/ui" ) type Options struct { diff --git a/internal/doctor/doctor_test.go b/apps/cli/internal/doctor/doctor_test.go similarity index 93% rename from internal/doctor/doctor_test.go rename to apps/cli/internal/doctor/doctor_test.go index f84314a..0bd08e4 100644 --- a/internal/doctor/doctor_test.go +++ b/apps/cli/internal/doctor/doctor_test.go @@ -11,10 +11,10 @@ import ( "testing" "time" - "plumb/internal/httpx" - "plumb/internal/keystone" - "plumb/internal/trace" - "plumb/internal/ui" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/keystone" + "github.com/PunGrumpy/plumb/apps/cli/internal/trace" + "github.com/PunGrumpy/plumb/apps/cli/internal/ui" ) // fakeCloud answers as Keystone, the OpenStack APIs, the Config API, a diff --git a/internal/glance/glance.go b/apps/cli/internal/glance/glance.go similarity index 92% rename from internal/glance/glance.go rename to apps/cli/internal/glance/glance.go index 2d6a645..263d709 100644 --- a/internal/glance/glance.go +++ b/apps/cli/internal/glance/glance.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) type Client struct { diff --git a/internal/httpx/client.go b/apps/cli/internal/httpx/client.go similarity index 100% rename from internal/httpx/client.go rename to apps/cli/internal/httpx/client.go diff --git a/internal/httpx/record.go b/apps/cli/internal/httpx/record.go similarity index 100% rename from internal/httpx/record.go rename to apps/cli/internal/httpx/record.go diff --git a/internal/httpx/record_test.go b/apps/cli/internal/httpx/record_test.go similarity index 100% rename from internal/httpx/record_test.go rename to apps/cli/internal/httpx/record_test.go diff --git a/internal/keystone/keystone.go b/apps/cli/internal/keystone/keystone.go similarity index 99% rename from internal/keystone/keystone.go rename to apps/cli/internal/keystone/keystone.go index 2efd7c9..6855b58 100644 --- a/internal/keystone/keystone.go +++ b/apps/cli/internal/keystone/keystone.go @@ -10,7 +10,7 @@ import ( "net/http" "strings" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) // Credentials mirror the OS_* variables that openrc exports. diff --git a/internal/keystone/keystone_test.go b/apps/cli/internal/keystone/keystone_test.go similarity index 100% rename from internal/keystone/keystone_test.go rename to apps/cli/internal/keystone/keystone_test.go diff --git a/internal/neutron/neutron.go b/apps/cli/internal/neutron/neutron.go similarity index 99% rename from internal/neutron/neutron.go rename to apps/cli/internal/neutron/neutron.go index 5611611..3d94001 100644 --- a/internal/neutron/neutron.go +++ b/apps/cli/internal/neutron/neutron.go @@ -9,7 +9,7 @@ import ( "slices" "strings" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) type Client struct { diff --git a/internal/nova/nova.go b/apps/cli/internal/nova/nova.go similarity index 98% rename from internal/nova/nova.go rename to apps/cli/internal/nova/nova.go index b96414e..f35d6c9 100644 --- a/internal/nova/nova.go +++ b/apps/cli/internal/nova/nova.go @@ -8,7 +8,7 @@ import ( "net/url" "regexp" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) // Microversion 2.47 embeds the flavor in the server body, which saves a diff --git a/internal/opensdn/agent/agent.go b/apps/cli/internal/opensdn/agent/agent.go similarity index 97% rename from internal/opensdn/agent/agent.go rename to apps/cli/internal/opensdn/agent/agent.go index eeb927e..bf70647 100644 --- a/internal/opensdn/agent/agent.go +++ b/apps/cli/internal/opensdn/agent/agent.go @@ -8,8 +8,8 @@ import ( "net/url" "strconv" - "plumb/internal/httpx" - "plumb/internal/opensdn/sandesh" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/sandesh" ) type Client struct { diff --git a/internal/opensdn/config/config.go b/apps/cli/internal/opensdn/config/config.go similarity index 99% rename from internal/opensdn/config/config.go rename to apps/cli/internal/opensdn/config/config.go index 171df8f..9f8c231 100644 --- a/internal/opensdn/config/config.go +++ b/apps/cli/internal/opensdn/config/config.go @@ -15,7 +15,7 @@ import ( "net/url" "strings" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) type Client struct { diff --git a/internal/opensdn/control/control.go b/apps/cli/internal/opensdn/control/control.go similarity index 95% rename from internal/opensdn/control/control.go rename to apps/cli/internal/opensdn/control/control.go index f9f7f47..337d3f6 100644 --- a/internal/opensdn/control/control.go +++ b/apps/cli/internal/opensdn/control/control.go @@ -7,8 +7,8 @@ import ( "context" "net/url" - "plumb/internal/httpx" - "plumb/internal/opensdn/sandesh" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/sandesh" ) type Client struct { diff --git a/internal/opensdn/sandesh/sandesh.go b/apps/cli/internal/opensdn/sandesh/sandesh.go similarity index 98% rename from internal/opensdn/sandesh/sandesh.go rename to apps/cli/internal/opensdn/sandesh/sandesh.go index 9bd6ba7..1939b73 100644 --- a/internal/opensdn/sandesh/sandesh.go +++ b/apps/cli/internal/opensdn/sandesh/sandesh.go @@ -25,7 +25,7 @@ import ( "strconv" "strings" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) // Node is one XML element. diff --git a/internal/opensdn/sandesh/sandesh_test.go b/apps/cli/internal/opensdn/sandesh/sandesh_test.go similarity index 100% rename from internal/opensdn/sandesh/sandesh_test.go rename to apps/cli/internal/opensdn/sandesh/sandesh_test.go diff --git a/internal/profile/profile.go b/apps/cli/internal/profile/profile.go similarity index 100% rename from internal/profile/profile.go rename to apps/cli/internal/profile/profile.go diff --git a/internal/render/tree.go b/apps/cli/internal/render/tree.go similarity index 99% rename from internal/render/tree.go rename to apps/cli/internal/render/tree.go index 62511eb..82a7ad0 100644 --- a/internal/render/tree.go +++ b/apps/cli/internal/render/tree.go @@ -8,8 +8,8 @@ import ( "strconv" "strings" - "plumb/internal/trace" - "plumb/internal/ui" + "github.com/PunGrumpy/plumb/apps/cli/internal/trace" + "github.com/PunGrumpy/plumb/apps/cli/internal/ui" ) type Options struct { diff --git a/internal/trace/issues.go b/apps/cli/internal/trace/issues.go similarity index 99% rename from internal/trace/issues.go rename to apps/cli/internal/trace/issues.go index d2fc82e..c3f0296 100644 --- a/internal/trace/issues.go +++ b/apps/cli/internal/trace/issues.go @@ -9,7 +9,7 @@ import ( "sort" "strings" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) // Issue is one problem a stage found. Code is stable: it is the key for diff --git a/internal/trace/model.go b/apps/cli/internal/trace/model.go similarity index 100% rename from internal/trace/model.go rename to apps/cli/internal/trace/model.go diff --git a/internal/trace/opensdn.go b/apps/cli/internal/trace/opensdn.go similarity index 98% rename from internal/trace/opensdn.go rename to apps/cli/internal/trace/opensdn.go index 58afb51..c3f72a4 100644 --- a/internal/trace/opensdn.go +++ b/apps/cli/internal/trace/opensdn.go @@ -7,10 +7,10 @@ import ( "slices" "strings" - "plumb/internal/httpx" - "plumb/internal/opensdn/agent" - "plumb/internal/opensdn/config" - "plumb/internal/opensdn/control" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/agent" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/config" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/control" ) var errNoVMI = errors.New("no virtual-machine-interface with this UUID; Neutron may not use the OpenSDN plugin, or the two are out of sync") diff --git a/internal/trace/openstack.go b/apps/cli/internal/trace/openstack.go similarity index 97% rename from internal/trace/openstack.go rename to apps/cli/internal/trace/openstack.go index a56b2d9..6c9475b 100644 --- a/internal/trace/openstack.go +++ b/apps/cli/internal/trace/openstack.go @@ -6,11 +6,11 @@ import ( "net" "strings" - "plumb/internal/glance" - "plumb/internal/httpx" - "plumb/internal/keystone" - "plumb/internal/neutron" - "plumb/internal/nova" + "github.com/PunGrumpy/plumb/apps/cli/internal/glance" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/keystone" + "github.com/PunGrumpy/plumb/apps/cli/internal/neutron" + "github.com/PunGrumpy/plumb/apps/cli/internal/nova" ) // keystone gets a token and resolves the endpoints the next stages call. diff --git a/internal/trace/path.go b/apps/cli/internal/trace/path.go similarity index 99% rename from internal/trace/path.go rename to apps/cli/internal/trace/path.go index 22482d7..609eb56 100644 --- a/internal/trace/path.go +++ b/apps/cli/internal/trace/path.go @@ -7,8 +7,8 @@ import ( "slices" "strings" - "plumb/internal/httpx" - "plumb/internal/opensdn/agent" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/opensdn/agent" ) // PathOptions names the traffic to check. Protocol is icmp, tcp or udp; diff --git a/internal/trace/path_test.go b/apps/cli/internal/trace/path_test.go similarity index 99% rename from internal/trace/path_test.go rename to apps/cli/internal/trace/path_test.go index 3604a04..548157a 100644 --- a/internal/trace/path_test.go +++ b/apps/cli/internal/trace/path_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "plumb/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" ) func ptr(n int) *int { return &n } diff --git a/internal/trace/trace.go b/apps/cli/internal/trace/trace.go similarity index 97% rename from internal/trace/trace.go rename to apps/cli/internal/trace/trace.go index e4a14d2..7fe0d12 100644 --- a/internal/trace/trace.go +++ b/apps/cli/internal/trace/trace.go @@ -15,8 +15,8 @@ import ( "strings" "time" - "plumb/internal/httpx" - "plumb/internal/keystone" + "github.com/PunGrumpy/plumb/apps/cli/internal/httpx" + "github.com/PunGrumpy/plumb/apps/cli/internal/keystone" ) // Options are the inputs to Run. Only HTTP and Creds are required; leave diff --git a/internal/ui/ui.go b/apps/cli/internal/ui/ui.go similarity index 100% rename from internal/ui/ui.go rename to apps/cli/internal/ui/ui.go diff --git a/internal/update/update.go b/apps/cli/internal/update/update.go similarity index 100% rename from internal/update/update.go rename to apps/cli/internal/update/update.go diff --git a/internal/update/update_test.go b/apps/cli/internal/update/update_test.go similarity index 100% rename from internal/update/update_test.go rename to apps/cli/internal/update/update_test.go diff --git a/docs/add-a-stage.md b/docs/add-a-stage.md index b5c53e0..df4e0ef 100644 --- a/docs/add-a-stage.md +++ b/docs/add-a-stage.md @@ -4,21 +4,21 @@ หน้านี้บอกขั้นตอนเพิ่ม stage ที่อ่าน API ใหม่ เช่น Octavia หรือ Cinder แล้วแสดงผลใน tree เหตุผลของโครงสร้างนี้อยู่ใน [plumb ออกแบบอย่างไร](architecture.md) -1. ถ้า API ยังไม่มี client ให้สร้าง package ใหม่ใต้ `internal/` ที่รับ `*httpx.Client` แล้วเรียกผ่าน `httpx.Service` -2. เพิ่ม field ใน `Trace` หรือ `Port` ใน `internal/trace/model.go` สำหรับผลของ stage +1. ถ้า API ยังไม่มี client ให้สร้าง package ใหม่ใต้ `apps/cli/internal/` ที่รับ `*httpx.Client` แล้วเรียกผ่าน `httpx.Service` +2. เพิ่ม field ใน `Trace` หรือ `Port` ใน `apps/cli/internal/trace/model.go` สำหรับผลของ stage 3. เขียน method บน `run` ที่มี signature `func(context.Context) error`: - ถ้าขาดข้อมูลจาก stage ก่อนหน้า ให้คืนค่าจาก `skip` - ถ้า API ตอบแต่ข้อมูลไม่ตรงกับชั้นอื่น ให้เรียก `r.warn` พร้อม code เช่น `r.warn("route-missing", …)` - ถ้า call หลักของ stage ไม่สำเร็จ ให้คืน error หรือคืนค่าจาก `fail` ถ้าคุณรู้สาเหตุและมี code ให้ -4. ถ้าใช้ code ใหม่ ให้เพิ่ม code, ความหมาย และสิ่งที่ต้องตรวจใน `explanations` ของ `internal/trace/issues.go` แล้วเพิ่มหัวข้อ `` ### `code` `` ใน `docs/troubleshooting.md` -5. เพิ่ม stage ในตาราง `stages` ของ `Run` ใน `internal/trace/trace.go` ตามลำดับที่ข้อมูลต้องใช้ -6. แสดงผลใน `internal/render/tree.go` -7. เพิ่มไฟล์ response ของ API ใหม่ใน `internal/demo/lab` ชื่อไฟล์ต้องตรงกับ `httpx.Key` ของ request ถ้าชื่อผิด stage จะ fail ด้วย error `replay: no recording` ตามด้วยชื่อไฟล์ที่ต้องใช้ +4. ถ้าใช้ code ใหม่ ให้เพิ่ม code, ความหมาย และสิ่งที่ต้องตรวจใน `explanations` ของ `apps/cli/internal/trace/issues.go` แล้วเพิ่มหัวข้อ `` ### `code` `` ใน `docs/troubleshooting.md` +5. เพิ่ม stage ในตาราง `stages` ของ `Run` ใน `apps/cli/internal/trace/trace.go` ตามลำดับที่ข้อมูลต้องใช้ +6. แสดงผลใน `apps/cli/internal/render/tree.go` +7. เพิ่มไฟล์ response ของ API ใหม่ใน `apps/cli/internal/demo/lab` ชื่อไฟล์ต้องตรงกับ `httpx.Key` ของ request ถ้าชื่อผิด stage จะ fail ด้วย error `replay: no recording` ตามด้วยชื่อไฟล์ที่ต้องใช้ 8. สร้าง golden file ใหม่แล้วตรวจ diff: ```sh - make golden - git diff cmd/plumb/testdata/demo.golden + make -C apps/cli golden + git diff apps/cli/cmd/plumb/testdata/demo.golden ``` -ถ้า diff แสดงเฉพาะบรรทัดของ stage ใหม่ ให้รัน `make test` แล้ว commit ไฟล์ทั้งหมด ถ้าอยากให้ผู้ใช้เห็นปัญหาของ stage ใหม่ใน `plumb demo` ให้เพิ่ม scenario ใน `internal/demo/demo.go` และเพิ่มกรณีใน `TestDemoScenarios` +ถ้า diff แสดงเฉพาะบรรทัดของ stage ใหม่ ให้รัน `make -C apps/cli test` แล้ว commit ไฟล์ทั้งหมด ถ้าอยากให้ผู้ใช้เห็นปัญหาของ stage ใหม่ใน `plumb demo` ให้เพิ่ม scenario ใน `apps/cli/internal/demo/demo.go` และเพิ่มกรณีใน `TestDemoScenarios` diff --git a/docs/api-reference.md b/docs/api-reference.md index d7706c0..8e21e99 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -2,7 +2,7 @@ # API ที่ plumb เรียกในแต่ละชั้น -หน้านี้รวม HTTP call ทุกตัวที่ plumb เรียก เรียงตาม stage และตามลำดับใน `internal/trace` แต่ละ call มี parameter, field ที่อ่าน และสิ่งที่ plumb ใช้ field นั้นทำ ไฟล์ใน `internal/demo/lab` เขียนตามรูปแบบในหน้านี้ ไม่ได้บันทึกจาก lab OpenSDN +หน้านี้รวม HTTP call ทุกตัวที่ plumb เรียก เรียงตาม stage และตามลำดับใน `apps/cli/internal/trace` แต่ละ call มี parameter, field ที่อ่าน และสิ่งที่ plumb ใช้ field นั้นทำ ไฟล์ใน `apps/cli/internal/demo/lab` เขียนตามรูปแบบในหน้านี้ ไม่ได้บันทึกจาก lab OpenSDN ## ความคงที่ของ field diff --git a/docs/architecture.md b/docs/architecture.md index 610721a..9418147 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,28 +6,29 @@ ## โครงสร้าง package -โค้ดแบ่งเป็น 4 กลุ่ม คือ client ของแต่ละ API, stage ที่ร้อยข้อมูลเข้าด้วยกัน, ส่วนแสดงผล และคำสั่งที่ช่วยผู้ใช้เริ่มต้น: +โค้ด Go อยู่ใน `apps/cli/` ซึ่งเป็น module `github.com/PunGrumpy/plumb/apps/cli` โค้ดแบ่งเป็น 4 กลุ่ม คือ client ของแต่ละ API, stage ที่ร้อยข้อมูลเข้าด้วยกัน, ส่วนแสดงผล และคำสั่งที่ช่วยผู้ใช้เริ่มต้น: ```text -cmd/plumb/ คำสั่ง, flag, transport และ exit code -internal/httpx/ HTTP client, --debug, --record, --replay -internal/keystone/ token และ service catalog -internal/nova/ server และ os-interface -internal/glance/ ชื่อ image -internal/neutron/ port, network, subnet, security group, FIP -internal/opensdn/ - config/ Config API (8082) - sandesh/ parser ของ introspect แบบ generic - control/ control node introspect (8083) - agent/ vRouter agent introspect (8085) -internal/trace/ struct Trace, 6 stage และ issue code -internal/render/ tree, verdict และ JSON -internal/ui/ สี และ spinner -internal/demo/ lab ที่ฝังใน binary และ scenario -internal/doctor/ ตรวจการเข้าถึงทุกชั้น +apps/cli/ + cmd/plumb/ คำสั่ง, flag, transport และ exit code + internal/httpx/ HTTP client, --debug, --record, --replay + internal/keystone/ token และ service catalog + internal/nova/ server และ os-interface + internal/glance/ ชื่อ image + internal/neutron/ port, network, subnet, security group, FIP + internal/opensdn/ + config/ Config API (8082) + sandesh/ parser ของ introspect แบบ generic + control/ control node introspect (8083) + agent/ vRouter agent introspect (8085) + internal/trace/ struct Trace, 6 stage และ issue code + internal/render/ tree, verdict และ JSON + internal/ui/ สี และ spinner + internal/demo/ lab ที่ฝังใน binary และ scenario + internal/doctor/ ตรวจการเข้าถึงทุกชั้น ``` -Client แต่ละตัวรู้จักเฉพาะ API ของตัวเอง และไม่ import กันเอง มีเพียง `internal/trace` ที่รู้ว่าค่าจากชั้นไหนต้องส่งต่อไปชั้นไหน +Client แต่ละตัวรู้จักเฉพาะ API ของตัวเอง และไม่ import กันเอง มีเพียง `apps/cli/internal/trace` ที่รู้ว่าค่าจากชั้นไหนต้องส่งต่อไปชั้นไหน ## การตัดสินใจ 7 ข้อและสิ่งที่ต้องยอมแลก @@ -47,11 +48,11 @@ Client ทุกตัวเรียก HTTP ผ่าน `httpx.Client` เพ - `--record` เป็น `http.RoundTripper` ที่บันทึก response ลงไฟล์ - `--replay` เป็น `http.RoundTripper` ที่ตอบจากไฟล์ -ไฟล์บันทึกเป็น HTTP response แบบข้อความ คนเขียนหรือแก้ไฟล์ด้วยมือได้ และ `http.ReadResponse` อ่านไฟล์กลับได้ ผลที่ได้คือ lab ใน `internal/demo/lab` ใช้ได้ทั้งใน `plumb demo` และใน test โดยไม่ต้องมี mock server +ไฟล์บันทึกเป็น HTTP response แบบข้อความ คนเขียนหรือแก้ไฟล์ด้วยมือได้ และ `http.ReadResponse` อ่านไฟล์กลับได้ ผลที่ได้คือ lab ใน `apps/cli/internal/demo/lab` ใช้ได้ทั้งใน `plumb demo` และใน test โดยไม่ต้องมี mock server ### `Trace` เป็นข้อตกลงเดียวระหว่าง stage กับการแสดงผล -Stage เขียนผลลง struct `Trace` ใน `internal/trace/model.go` และ renderer อ่านจาก struct นั้นเท่านั้น tree, `--json` และหน้าเว็บในอนาคตจึงแสดงข้อมูลชุดเดียวกัน +Stage เขียนผลลง struct `Trace` ใน `apps/cli/internal/trace/model.go` และ renderer อ่านจาก struct นั้นเท่านั้น tree, `--json` และหน้าเว็บในอนาคตจึงแสดงข้อมูลชุดเดียวกัน `Trace` เก็บผลแยกตาม port เพราะ UUID ของ port เป็นค่าเดียวที่ใช้ได้ตั้งแต่ Nova ถึง vRouter agent VM ที่มี 2 port จึงมี `Port` 2 ตัว และแต่ละตัวเก็บผลของทุกชั้น @@ -67,7 +68,7 @@ Stage `neutron` ค้น port ด้วย `device_id` และไม่ใช Introspect ตอบเป็น XML ของ Sandesh และชื่อ field เปลี่ยนได้ตาม release package `sandesh` จึง parse XML เป็น tree ของ `Node` แล้ว client อ่าน field ตามชื่อด้วย `Str`, `Int` และ `Strings` -ถ้า release ของคุณไม่มี field ใด ค่าที่อ่านได้จะว่าง ไม่ใช่ error การแก้ชื่อ field ทำใน `internal/opensdn/control` หรือ `internal/opensdn/agent` โดยไม่แตะ parser ข้อเสียคือ compiler ไม่เตือนเมื่อชื่อ field ผิด test ของ lab จำลองจึงต้องครอบคลุมทุก field ที่ tree แสดง +ถ้า release ของคุณไม่มี field ใด ค่าที่อ่านได้จะว่าง ไม่ใช่ error การแก้ชื่อ field ทำใน `apps/cli/internal/opensdn/control` หรือ `apps/cli/internal/opensdn/agent` โดยไม่แตะ parser ข้อเสียคือ compiler ไม่เตือนเมื่อชื่อ field ผิด test ของ lab จำลองจึงต้องครอบคลุมทุก field ที่ tree แสดง ### อ่านอย่างเดียว และไม่ส่ง token ไปที่ introspect @@ -75,7 +76,7 @@ plumb ส่ง `POST` ครั้งเดียวไปที่ Keystone ### ทุกปัญหามี code, hint และคำสั่งที่อธิบายต่อ -คำเตือนและ error ที่ plumb รู้สาเหตุมี code คงที่ เช่น `route-missing` แต่ละ code มีความหมายและสิ่งที่ต้องตรวจต่อใน `internal/trace/issues.go` ข้อมูลชุดนั้นใช้ใน 4 ที่ คือ บรรทัด `Hint` ท้าย tree, คำสั่ง `plumb explain`, ฟิลด์ `code` และ `hint` ใน JSON และหัวข้อใน `docs/troubleshooting.md` +คำเตือนและ error ที่ plumb รู้สาเหตุมี code คงที่ เช่น `route-missing` แต่ละ code มีความหมายและสิ่งที่ต้องตรวจต่อใน `apps/cli/internal/trace/issues.go` ข้อมูลชุดนั้นใช้ใน 4 ที่ คือ บรรทัด `Hint` ท้าย tree, คำสั่ง `plumb explain`, ฟิลด์ `code` และ `hint` ใน JSON และหัวข้อใน `docs/troubleshooting.md` ผมเลือกทางนี้เพราะคนที่เปิด plumb ตอนระบบพังต้องการรู้ 2 อย่าง คืออะไรพังและต้องทำอะไรต่อ ข้อความ error ของ Go บอกได้เฉพาะอย่างแรก ราคาที่ต้องจ่ายคือ code ใหม่ทุกตัวต้องมีคำอธิบายและหัวข้อในเอกสาร test `TestEveryCodeIsDocumented` จึง fail ถ้าหัวข้อหายไป @@ -85,6 +86,6 @@ plumb ส่ง `POST` ครั้งเดียวไปที่ Keystone โปรเจกต์เสริม 3 ตัวใช้ package ของ plumb ได้โดยตรง: -- หน้าเว็บ object graph ของ OpenSDN อ่านผลของ `plumb --json` หรือเรียก `internal/opensdn/config` เอง `Ref` ทุกตัวมี `to` และ `uuid` ของปลายทาง จึงสร้าง edge ได้โดยไม่ต้อง `GET` เพิ่ม +- หน้าเว็บ object graph ของ OpenSDN อ่านผลของ `plumb --json` หรือเรียก `apps/cli/internal/opensdn/config` เอง `Ref` ทุกตัวมี `to` และ `uuid` ของปลายทาง จึงสร้าง edge ได้โดยไม่ต้อง `GET` เพิ่ม - ตัวตรวจ drift ระหว่าง Neutron กับ OpenSDN เพิ่ม method ที่ list object ใน client ของ `neutron` และ `config` แล้วเทียบ UUID ของ port กับ UUID ของ VMI -- Control plane จำลองใช้ struct ใน `internal/trace/model.go` เป็นต้นแบบของ message ระหว่าง controller กับ agent จำลอง +- Control plane จำลองใช้ struct ใน `apps/cli/internal/trace/model.go` เป็นต้นแบบของ message ระหว่าง controller กับ agent จำลอง diff --git a/docs/cli-reference.md b/docs/cli-reference.md index bf2dbfb..3640362 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -2,7 +2,7 @@ # ตัวเลือกของ plumb -หน้านี้รวมคำสั่ง, flag, environment variable, exit code และรูปแบบ JSON ของ `plumb` ตรงกับโค้ดใน `cmd/plumb` และผลของ `plumb -h` +หน้านี้รวมคำสั่ง, flag, environment variable, exit code และรูปแบบ JSON ของ `plumb` ตรงกับโค้ดใน `apps/cli/cmd/plumb` และผลของ `plumb -h` ## คำสั่ง @@ -170,7 +170,7 @@ Content-Type: application/json {"server": {…}} ``` -ไฟล์เก็บเฉพาะ header `Content-Type` และ `X-Subject-Token` และ `--record` เขียน `recorded-token` แทนค่าจริงของ `X-Subject-Token` ไฟล์ไม่เก็บ request จึงไม่มี password lab ของ `plumb demo` ใช้รูปแบบเดียวกันและอยู่ใน `internal/demo/lab` +ไฟล์เก็บเฉพาะ header `Content-Type` และ `X-Subject-Token` และ `--record` เขียน `recorded-token` แทนค่าจริงของ `X-Subject-Token` ไฟล์ไม่เก็บ request จึงไม่มี password lab ของ `plumb demo` ใช้รูปแบบเดียวกันและอยู่ใน `apps/cli/internal/demo/lab` ## Exit code @@ -193,7 +193,7 @@ Content-Type: application/json ## JSON ของ trace -`--json` พิมพ์ struct `Trace` ใน `internal/trace/model.go` แต่ละ stage ใน `steps` มี field เหล่านี้: +`--json` พิมพ์ struct `Trace` ใน `apps/cli/internal/trace/model.go` แต่ละ stage ใน `steps` มี field เหล่านี้: | Field | ความหมาย | | --- | --- | diff --git a/docs/quickstart.md b/docs/quickstart.md index 3bc20cd..28e9a79 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -9,13 +9,13 @@ เราต้องใช้ Go 1.24 ขึ้นไป ที่ root ของ repo ให้ build binary: ```sh -make build +make -C apps/cli build ``` ตรวจว่า binary ทำงาน: ```sh -./bin/plumb version +./apps/cli/bin/plumb version ``` ผลลัพธ์คือ `plumb` ตามด้วย version เช่น `plumb dev` @@ -25,7 +25,7 @@ make build ตอนนี้ให้รัน demo: ```sh -./bin/plumb demo +./apps/cli/bin/plumb demo ``` บรรทัดแรกบอก scenario ที่กำลังรัน ตามด้วย tree ที่ขึ้นต้นด้วย `VM web-01` ส่วนท้ายของผลลัพธ์เป็นแบบนี้: @@ -69,7 +69,7 @@ Steps DevStack ใช้ OVN จึงไม่มี Config API ของ OpenSDN ให้เรียก scenario `devstack` รัน lab เดิมโดยไม่มี Config API: ```sh -./bin/plumb demo devstack +./apps/cli/bin/plumb demo devstack ``` ส่วนท้ายเปลี่ยนเป็นแบบนี้: @@ -90,7 +90,7 @@ port ใน scenario นี้มี `vif_type` เป็น `ovs` plumb จึ ต่อไปเราจะเปิด scenario ที่ `control-02` ไม่มี route ของ VM: ```sh -./bin/plumb demo missing-route +./apps/cli/bin/plumb demo missing-route ``` บรรทัด `Control` ของ `control-02` ใน tree กลายเป็น `10.0.1.5/32 ✗ missing` และผลลัพธ์จบด้วย 3 บรรทัดนี้: @@ -104,7 +104,7 @@ port ใน scenario นี้มี `vif_type` เป็น `ovs` plumb จึ บรรทัด `Hint` บอกสิ่งที่ต้องตรวจต่อ และบรรทัด `More` บอกคำสั่งที่อธิบายปัญหานี้ ให้รันคำสั่งนั้น: ```sh -./bin/plumb explain route-missing +./apps/cli/bin/plumb explain route-missing ``` plumb พิมพ์ความหมายของ `route-missing`, สิ่งที่ต้องตรวจ และหัวข้อใน [คำเตือนแต่ละข้อของ plumb หมายถึงอะไร](troubleshooting.md) @@ -114,7 +114,7 @@ plumb พิมพ์ความหมายของ `route-missing`, สิ ขั้นสุดท้าย ให้ดูรายการ scenario ทั้งหมด: ```sh -./bin/plumb demo --list +./apps/cli/bin/plumb demo --list ``` แต่ละ scenario ทำให้ชั้นหนึ่งพัง ลองรัน `agent-down` แล้วสังเกตว่าเครื่องหมายหน้าบรรทัดสุดท้ายเปลี่ยนเป็น `✗` และคำสั่งจบด้วย exit code `1` เพราะ stage `vrouter` เรียก API ไม่สำเร็จ ส่วน `missing-route` จบด้วย exit code `0` เพราะ API ทุกตัวตอบปกติ มีเพียงข้อมูลที่ไม่ตรงกัน diff --git a/docs/releasing.md b/docs/releasing.md index 8c610bd..aa1d3e9 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -53,7 +53,7 @@ GoReleaser ใส่ค่าเหล่านี้ตอน build ผ่า - `main.version` เป็นชื่อ tag เช่น `v0.2.0` ซึ่ง `plumb version` แสดง - `main.updateURL` เป็น `https://api.github.com/repos///releases/latest` ของ repo ที่ build ทำให้ binary แจ้งเมื่อมี release ใหม่ -ถ้า build เองด้วย `make dist` โดยไม่ตั้ง `UPDATE_URL` binary จะไม่เช็ก version ใหม่ +ถ้า build เองด้วย `make -C apps/cli dist` โดยไม่ตั้ง `UPDATE_URL` binary จะไม่เช็ก version ใหม่ ## ตรวจ config ก่อน push diff --git a/docs/run-against-a-lab.md b/docs/run-against-a-lab.md index 7527f06..84fa29e 100644 --- a/docs/run-against-a-lab.md +++ b/docs/run-against-a-lab.md @@ -11,20 +11,20 @@ plumb ต้องเข้าถึง introspect ของ vRouter agent บ 1. Build binary แบบ static บนเครื่องของคุณ: ```sh - make dist + make -C apps/cli dist ``` - คำสั่งนี้สร้าง `dist/plumb-linux-amd64`, `dist/plumb-linux-arm64` และ `dist/plumb-darwin-arm64` ถ้าต้องการให้ binary แจ้งเมื่อมี version ใหม่ ให้ส่ง URL ที่ตอบ release ล่าสุด: + คำสั่งนี้สร้าง `apps/cli/dist/plumb-linux-amd64`, `apps/cli/dist/plumb-linux-arm64` และ `apps/cli/dist/plumb-darwin-arm64` ถ้าต้องการให้ binary แจ้งเมื่อมี version ใหม่ ให้ส่ง URL ที่ตอบ release ล่าสุด: ```sh - make dist VERSION=v0.1.0 \ + make -C apps/cli dist VERSION=v0.1.0 \ UPDATE_URL=https://api.github.com/repos/your_org/plumb/releases/latest ``` 2. คัดลอก binary ที่ตรงกับ server: ```sh - scp dist/plumb-linux-amd64 your_user@your_bastion:~/bin/plumb + scp apps/cli/dist/plumb-linux-amd64 your_user@your_bastion:~/bin/plumb ``` binary ไม่ต้องใช้ library อื่น server จึงไม่ต้องติดตั้ง Go @@ -124,7 +124,7 @@ plumb ตรวจ port, router, security group ทั้ง 2 ฝั่ง แ 1. บันทึก lab ตามหัวข้อก่อนหน้า 2. เปิดไฟล์ที่ชื่อขึ้นต้นด้วย `GET_` ตามด้วย IP ของ control node หรือ compute 3. เทียบชื่อ element ในไฟล์กับตารางใน [API ที่ plumb เรียกในแต่ละชั้น](api-reference.md) -4. ถ้าชื่อไม่ตรง ให้แก้ชื่อใน `internal/opensdn/control/control.go` หรือ `internal/opensdn/agent/agent.go` +4. ถ้าชื่อไม่ตรง ให้แก้ชื่อใน `apps/cli/internal/opensdn/control/control.go` หรือ `apps/cli/internal/opensdn/agent/agent.go` อีกทางหนึ่งคือเปิด `http://control_node_ip:8083/` ใน browser หน้านั้นแสดงรายการ request ทั้งหมดของ process diff --git a/go.mod b/go.mod deleted file mode 100644 index f3d85f2..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module plumb - -go 1.24 From 7428997368980699966a2561f3322805085743a2 Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:36:56 +0700 Subject: [PATCH 02/20] build: run tasks with Turborepo and Bun Turborepo reads the Go module through go.work, so build, test and check run with caching from the repo root. Bun replaces npm for the dev tools. Changesets ignores the root package once it has workspaces, so the version and CHANGELOG.md move to apps/cli/package.json. --- .changeset/README.md | 2 +- .github/workflows/ci.yml | 15 +- .github/workflows/release.yml | 21 +- .gitignore | 2 + .goreleaser.yaml | 2 +- AGENTS.md | 10 + CHANGELOG.md => apps/cli/CHANGELOG.md | 0 apps/cli/package.json | 7 + bun.lock | 114 ++++++ docs/releasing.md | 18 +- go.work | 3 + package-lock.json | 548 -------------------------- package.json | 17 +- turbo.json | 25 ++ 14 files changed, 196 insertions(+), 588 deletions(-) create mode 100644 AGENTS.md rename CHANGELOG.md => apps/cli/CHANGELOG.md (100%) create mode 100644 apps/cli/package.json create mode 100644 bun.lock create mode 100644 go.work delete mode 100644 package-lock.json create mode 100644 turbo.json diff --git a/.changeset/README.md b/.changeset/README.md index e6d649f..ae52063 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,3 +1,3 @@ # Changesets -Each file in this directory describes one change for the next release. Run `npx changeset` to add one, and see [docs/releasing.md](../docs/releasing.md) for the whole flow. +Each file in this directory describes one change for the next release. Run `bun changeset` to add one, and see [docs/releasing.md](../docs/releasing.md) for the whole flow. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a75ee89..9a2023c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,16 +16,9 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: apps/cli/go.mod - - name: Check formatting - run: | - unformatted=$(gofmt -l apps/cli) - if [ -n "$unformatted" ]; then - echo "Run gofmt -w on:" && echo "$unformatted" - exit 1 - fi - - run: go vet ./... - working-directory: apps/cli - - run: go test -race ./... - working-directory: apps/cli + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + # check: gofmt and go vet for every Go module. + - run: bun turbo run check test build - name: Cross-compile every release platform run: make -C apps/cli dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index deb325a..6aae334 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,36 +17,33 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - run: npm ci + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile # While .changeset/ holds changesets, keep the "Version Packages" PR up - # to date. Merging it bumps package.json and writes CHANGELOG.md. + # to date. Merging it bumps apps/cli/package.json and writes apps/cli/CHANGELOG.md. - uses: changesets/action@v1 id: changesets with: - version: npm run version + version: bun run version env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # After that PR is merged, no changesets are left and package.json holds - # a version without a tag. Tag it and keep its CHANGELOG.md section as - # the release notes. + # After that PR is merged, no changesets are left and apps/cli/package.json + # holds a version without a tag. Tag it and keep its CHANGELOG.md section + # as the release notes. - name: Tag the new version id: tag if: steps.changesets.outputs.hasChangesets == 'false' run: | - version="$(node -p "require('./package.json').version")" + version="$(jq -r .version apps/cli/package.json)" if [ "$version" = "0.0.0" ] || git rev-parse -q --verify "refs/tags/v$version" >/dev/null; then echo "created=false" >> "$GITHUB_OUTPUT" exit 0 fi git tag "v$version" git push origin "v$version" - awk -v v="$version" '$0 == "## " v {f=1; next} /^## /{f=0} f' CHANGELOG.md > "$RUNNER_TEMP/notes.md" + awk -v v="$version" '$0 == "## " v {f=1; next} /^## /{f=0} f' apps/cli/CHANGELOG.md > "$RUNNER_TEMP/notes.md" echo "created=true" >> "$GITHUB_OUTPUT" - if: steps.tag.outputs.created == 'true' diff --git a/.gitignore b/.gitignore index e1ba796..775780d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ bin/ dist/ lab-recordings/ node_modules/ +/apps/cli/plumb +.turbo/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 6d5a91b..3563554 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -25,6 +25,6 @@ archives: checksum: name_template: checksums.txt -# The release workflow passes the version's section of CHANGELOG.md, which +# The release workflow passes the version's section of apps/cli/CHANGELOG.md, which # Changesets writes, as --release-notes. Keep the changelog pipe enabled: # disabling it also skips reading --release-notes. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3bad16e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,10 @@ + + +# This is NOT the Turborepo you know + +Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`. + +Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access. + +This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation. + diff --git a/CHANGELOG.md b/apps/cli/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to apps/cli/CHANGELOG.md diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 0000000..d0ca4ca --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,7 @@ +{ + "name": "plumb", + "version": "0.1.0", + "private": true, + "description": "Follow a VM through every layer of OpenStack and OpenSDN, from Keystone to the vRouter, in one tree.", + "license": "UNLICENSED" +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..24fdd80 --- /dev/null +++ b/bun.lock @@ -0,0 +1,114 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "plumb-monorepo", + "devDependencies": { + "@changesets/cli": "^3.0.3", + "turbo": "^2.11.5", + }, + }, + "apps/cli": { + "name": "plumb", + "version": "0.1.0", + }, + }, + "packages": { + "@changesets/apply-release-plan": ["@changesets/apply-release-plan@8.1.1", "", { "dependencies": { "@changesets/config": "^4.0.1", "@changesets/format": "^0.1.2", "@changesets/git": "^4.0.1", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "import-meta-resolve": "^4.2.0", "jsonc-parser": "^3.3.1", "semver": "^7.8.1" } }, "sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q=="], + + "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@7.0.0", "", { "dependencies": { "@changesets/errors": "^1.0.0", "@changesets/get-dependents-graph": "^3.0.0", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "semver": "^7.8.1" } }, "sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg=="], + + "@changesets/changelog-git": ["@changesets/changelog-git@1.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0" } }, "sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA=="], + + "@changesets/cli": ["@changesets/cli@3.0.3", "", { "dependencies": { "@changesets/apply-release-plan": "^8.1.1", "@changesets/assemble-release-plan": "^7.0.0", "@changesets/changelog-git": "^1.0.0", "@changesets/config": "^4.0.1", "@changesets/errors": "^1.0.0", "@changesets/get-dependents-graph": "^3.0.0", "@changesets/git": "^4.0.1", "@changesets/pre": "^3.0.0", "@changesets/read": "^1.0.1", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "@changesets/write": "^1.0.1", "@clack/prompts": "^1.7.0", "@manypkg/get-packages": "^3.1.0", "@pnpm/deps.graph-sequencer": "^1100.0.1", "cac": "^7.0.0", "import-meta-resolve": "^4.2.0", "launch-editor": "^2.14.1", "package-manager-detector": "^1.6.0", "semver": "^7.8.1", "tinyexec": "^1.3.1" }, "bin": { "changeset": "bin.js" } }, "sha512-L1i67bMfkK6iC/kDejuNO4pFgTRcWIObsD6Qof2woP1I4M8tj17DGlcpRaTzTv1DYSP5EMwWdEtR8X0Pj5Jg3Q=="], + + "@changesets/config": ["@changesets/config@4.0.1", "", { "dependencies": { "@changesets/get-dependents-graph": "^3.0.0", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "@manypkg/get-packages": "^3.1.0", "picomatch": "^4.0.7" } }, "sha512-PxpxSQLQSLDjceAgRq+4FWlU+HflDIw03mmPVipxoQx9xrXFUjmqTy3SE+n/2t+DqPdpPiLWVr5NRiLZvjyLwg=="], + + "@changesets/errors": ["@changesets/errors@1.0.0", "", {}, "sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g=="], + + "@changesets/format": ["@changesets/format@0.1.2", "", { "dependencies": { "package-manager-detector": "^1.8.0", "tinyexec": "^1.3.0" } }, "sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA=="], + + "@changesets/get-dependents-graph": ["@changesets/get-dependents-graph@3.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0", "semver": "^7.8.1" } }, "sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg=="], + + "@changesets/git": ["@changesets/git@4.0.1", "", { "dependencies": { "@changesets/errors": "^1.0.0", "@changesets/types": "^7.0.0", "@manypkg/get-packages": "^3.1.0", "picomatch": "^4.0.4", "tinyexec": "^1.3.0" } }, "sha512-6vWpIAC4LkpmlqaIu37ViT5enyd9+uBwAiGqCGhASvkSHBgx4PrtTGXWTMFYpDmZbjgyonyVgMciPrW4MqtJsA=="], + + "@changesets/parse": ["@changesets/parse@1.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0", "yaml": "^2.9.0" } }, "sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg=="], + + "@changesets/pre": ["@changesets/pre@3.0.0", "", { "dependencies": { "@changesets/errors": "^1.0.0", "@changesets/types": "^7.0.0", "@manypkg/get-packages": "^3.1.0" } }, "sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q=="], + + "@changesets/read": ["@changesets/read@1.0.1", "", { "dependencies": { "@changesets/git": "^4.0.1", "@changesets/parse": "^1.0.0", "@changesets/types": "^7.0.0" } }, "sha512-nzvSC6RcTiWf/dsuwZFXpLzGGsRHYCL68U3uHV7srsulU93aVWY7u2GEJiDZ+4GIIElfaW5EqtKC0UHroY+LTQ=="], + + "@changesets/should-skip-package": ["@changesets/should-skip-package@1.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0" } }, "sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg=="], + + "@changesets/types": ["@changesets/types@7.0.0", "", {}, "sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w=="], + + "@changesets/write": ["@changesets/write@1.0.1", "", { "dependencies": { "@changesets/format": "^0.1.1", "@changesets/types": "^7.0.0", "human-id": "^4.2.0" } }, "sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA=="], + + "@clack/core": ["@clack/core@1.5.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ=="], + + "@clack/prompts": ["@clack/prompts@1.8.1", "", { "dependencies": { "@clack/core": "1.5.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ=="], + + "@manypkg/find-root": ["@manypkg/find-root@3.1.0", "", { "dependencies": { "@manypkg/tools": "^2.1.0" } }, "sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA=="], + + "@manypkg/get-packages": ["@manypkg/get-packages@3.1.0", "", { "dependencies": { "@manypkg/find-root": "^3.1.0", "@manypkg/tools": "^2.1.0" } }, "sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg=="], + + "@manypkg/tools": ["@manypkg/tools@2.1.2", "", { "dependencies": { "jju": "^1.4.0", "tinyglobby": "^0.2.13", "yaml": "^2.9.0" } }, "sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ=="], + + "@pnpm/deps.graph-sequencer": ["@pnpm/deps.graph-sequencer@1100.0.1", "", {}, "sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A=="], + + "@turbo/darwin-64": ["@turbo/darwin-64@2.11.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-KdY4y7BBjIisl+jAkb3FFaRjClGqHyaeHCcVcCT8LBzFFwUu6thgfxyRZAx0uL4KoxLmH+iOan8UhVf/aDDcaA=="], + + "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.11.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ToZDGi2u410+TfRtC/O1/LpujGgvNJoRBZnwVXob7oO0+3OSlwIO3zofXKUvwQvlba7a8ZXPyCZwufjAXd7f0w=="], + + "@turbo/linux-64": ["@turbo/linux-64@2.11.5", "", { "os": [ "linux", "android", ], "cpu": "x64" }, "sha512-Ub2d/nAbdWLaSytcEaPp1wm/YuIvHEIqNrCRMI/UgOGyFdOhgxm8VyWPAJilQWRYxYVrqEUoy2K5S5K8SG50rA=="], + + "@turbo/linux-arm64": ["@turbo/linux-arm64@2.11.5", "", { "os": [ "linux", "android", ], "cpu": "arm64" }, "sha512-JQjmGxp724Wuft9FAxPMtmBur1CEdUyxJ+FVoIvKkgj63rXHl9PYCWrsU2tMjVi609XTQ3M8vZD9Yc9qVAVYrw=="], + + "@turbo/windows-64": ["@turbo/windows-64@2.11.5", "", { "os": "win32", "cpu": "x64" }, "sha512-AFkbPmXNd8XgCi/66YEHR2bVl3gtusXyDihTIVEPAuSB+7ulLmJePvz1unWPVKLHdbCTTIdKq2mpJ1B1e3lJXw=="], + + "@turbo/windows-arm64": ["@turbo/windows-arm64@2.11.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-52ziXYP3snGOxjv19xA3WL1WDKv9Q6uDAa/7EKIR5j4hocMlYURkrOHbJ2ZZI+VfPU5MwKpcGeRfHxHJu7Y/Pw=="], + + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "human-id": ["human-id@4.2.1", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "launch-editor": ["launch-editor@2.14.1", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.4" } }, "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA=="], + + "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + + "plumb": ["plumb@workspace:apps/cli"], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "shell-quote": ["shell-quote@1.11.0", "", {}, "sha512-JdxDPD0DBTyu08pq0kPC0xSNet/qsU07qT6IsX1AS8oO2ICNRY4ldNa8OAI6PuwAH8tG3lxEhbqmyp4Dw4036g=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "turbo": ["turbo@2.11.5", "", { "optionalDependencies": { "@turbo/darwin-64": "2.11.5", "@turbo/darwin-arm64": "2.11.5", "@turbo/linux-64": "2.11.5", "@turbo/linux-arm64": "2.11.5", "@turbo/windows-64": "2.11.5", "@turbo/windows-arm64": "2.11.5" }, "bin": { "turbo": "bin/turbo" } }, "sha512-qLXheqz3TUvJpHIV6MUBwkNkvwKxMVG/QsimyiKT9DfNWPHJ294G/i5nDaH+fbHJt3UH7orubNNxAqYkbUNGAQ=="], + + "yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], + } +} diff --git a/docs/releasing.md b/docs/releasing.md index aa1d3e9..5931568 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -2,24 +2,24 @@ # วิธีออก release ของ plumb -หน้านี้บอกขั้นตอนตั้งแต่บันทึกการเปลี่ยนแปลงจนได้ binary บน GitHub Release plumb ใช้ Changesets จัดการ version และ `CHANGELOG.md` และใช้ GoReleaser build binary ทั้งสองทำงานใน `.github/workflows/release.yml` +หน้านี้บอกขั้นตอนตั้งแต่บันทึกการเปลี่ยนแปลงจนได้ binary บน GitHub Release plumb ใช้ Changesets จัดการ version และ `apps/cli/CHANGELOG.md` และใช้ GoReleaser build binary ทั้งสองทำงานใน `.github/workflows/release.yml` -Version ของ plumb อยู่ใน `package.json` ไฟล์นี้เป็น `private` และมีไว้ให้ Changesets ใช้เท่านั้น ตัว plumb ไม่ได้ใช้ Node +Version ของ plumb อยู่ใน `apps/cli/package.json` ไฟล์นี้เป็น `private` และมีไว้ให้ Changesets ใช้เท่านั้น ตัว plumb ไม่ได้ใช้ Node ## เพิ่ม changeset ใน PR ทุก PR ที่ผู้ใช้ควรรู้ ต้องมี changeset 1 ไฟล์ -1. ติดตั้ง Changesets ครั้งแรกบนเครื่อง: +1. ติดตั้ง Changesets ครั้งแรกบนเครื่องด้วย [Bun](https://bun.sh): ```sh - npm install + bun install ``` 2. สร้าง changeset แล้วเลือกระดับของการเปลี่ยนแปลง: ```sh - npx changeset + bun changeset ``` 3. เขียนสรุปสำหรับผู้ใช้ ข้อความนี้จะอยู่ใน `CHANGELOG.md` และ release notes ตามที่เขียน @@ -38,13 +38,13 @@ PR ที่ผู้ใช้ไม่เห็นผล เช่นแก้ ## ออก release 1. Merge PR ที่มี changeset เข้า `main` -2. Workflow เปิดหรืออัปเดต PR ชื่อ "Version Packages" ซึ่ง bump version ใน `package.json` เขียน `CHANGELOG.md` และลบไฟล์ changeset ที่ใช้แล้ว -3. ตรวจ `CHANGELOG.md` ใน PR นั้น ถ้าต้องการแก้ข้อความ ให้แก้ใน PR ได้เลย +2. Workflow เปิดหรืออัปเดต PR ชื่อ "Version Packages" ซึ่ง bump version ใน `apps/cli/package.json` เขียน `apps/cli/CHANGELOG.md` และลบไฟล์ changeset ที่ใช้แล้ว +3. ตรวจ `apps/cli/CHANGELOG.md` ใน PR นั้น ถ้าต้องการแก้ข้อความ ให้แก้ใน PR ได้เลย 4. Merge PR "Version Packages" เมื่อพร้อมออก release PR "Version Packages" รวม changeset ทุกไฟล์ที่ merge เข้ามาจนถึงตอนนั้น ถ้ายังไม่อยากออก release ให้เปิด PR นั้นค้างไว้ -หลัง merge workflow สร้าง tag `vx.y.z` จาก version ใน `package.json` แล้ว GoReleaser build binary สำหรับ Linux และ macOS ทั้ง `amd64` และ `arm64` สร้าง GitHub Release ที่ใช้ส่วนของ version นั้นใน `CHANGELOG.md` เป็น release notes และแนบไฟล์ `.tar.gz` กับ `checksums.txt` +หลัง merge workflow สร้าง tag `vx.y.z` จาก version ใน `apps/cli/package.json` แล้ว GoReleaser build binary สำหรับ Linux และ macOS ทั้ง `amd64` และ `arm64` สร้าง GitHub Release ที่ใช้ส่วนของ version นั้นใน `apps/cli/CHANGELOG.md` เป็น release notes และแนบไฟล์ `.tar.gz` กับ `checksums.txt` ## สิ่งที่ release ใส่ใน binary @@ -64,4 +64,4 @@ goreleaser check goreleaser build --snapshot --clean --single-target ``` -ถ้าแก้ไฟล์ใน `.github/workflows/` ให้ตรวจด้วย `actionlint` ถ้าแก้ `.changeset/config.json` ให้ดูผลด้วย `npx changeset status` +ถ้าแก้ไฟล์ใน `.github/workflows/` ให้ตรวจด้วย `actionlint` ถ้าแก้ `.changeset/config.json` ให้ดูผลด้วย `bun changeset status` diff --git a/go.work b/go.work new file mode 100644 index 0000000..3697af8 --- /dev/null +++ b/go.work @@ -0,0 +1,3 @@ +go 1.24 + +use ./apps/cli diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 658065d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,548 +0,0 @@ -{ - "name": "plumb", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "plumb", - "version": "0.0.0", - "license": "UNLICENSED", - "devDependencies": { - "@changesets/cli": "^3.0.3" - } - }, - "node_modules/@changesets/apply-release-plan": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-8.1.1.tgz", - "integrity": "sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/config": "^4.0.1", - "@changesets/format": "^0.1.2", - "@changesets/git": "^4.0.1", - "@changesets/should-skip-package": "^1.0.0", - "@changesets/types": "^7.0.0", - "import-meta-resolve": "^4.2.0", - "jsonc-parser": "^3.3.1", - "semver": "^7.8.1" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/assemble-release-plan": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-7.0.0.tgz", - "integrity": "sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^1.0.0", - "@changesets/get-dependents-graph": "^3.0.0", - "@changesets/should-skip-package": "^1.0.0", - "@changesets/types": "^7.0.0", - "semver": "^7.8.1" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/changelog-git": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-1.0.0.tgz", - "integrity": "sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^7.0.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/cli": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-3.0.3.tgz", - "integrity": "sha512-L1i67bMfkK6iC/kDejuNO4pFgTRcWIObsD6Qof2woP1I4M8tj17DGlcpRaTzTv1DYSP5EMwWdEtR8X0Pj5Jg3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/apply-release-plan": "^8.1.1", - "@changesets/assemble-release-plan": "^7.0.0", - "@changesets/changelog-git": "^1.0.0", - "@changesets/config": "^4.0.1", - "@changesets/errors": "^1.0.0", - "@changesets/get-dependents-graph": "^3.0.0", - "@changesets/git": "^4.0.1", - "@changesets/pre": "^3.0.0", - "@changesets/read": "^1.0.1", - "@changesets/should-skip-package": "^1.0.0", - "@changesets/types": "^7.0.0", - "@changesets/write": "^1.0.1", - "@clack/prompts": "^1.7.0", - "@manypkg/get-packages": "^3.1.0", - "@pnpm/deps.graph-sequencer": "^1100.0.1", - "cac": "^7.0.0", - "import-meta-resolve": "^4.2.0", - "launch-editor": "^2.14.1", - "package-manager-detector": "^1.6.0", - "semver": "^7.8.1", - "tinyexec": "^1.3.1" - }, - "bin": { - "changeset": "bin.js" - }, - "engines": { - "node": "^22.11 || ^24 || >=26", - "npm": ">=10.9.0", - "pnpm": ">=10.0.0", - "yarn": ">=4.5.2" - } - }, - "node_modules/@changesets/config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-4.0.1.tgz", - "integrity": "sha512-PxpxSQLQSLDjceAgRq+4FWlU+HflDIw03mmPVipxoQx9xrXFUjmqTy3SE+n/2t+DqPdpPiLWVr5NRiLZvjyLwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/get-dependents-graph": "^3.0.0", - "@changesets/should-skip-package": "^1.0.0", - "@changesets/types": "^7.0.0", - "@manypkg/get-packages": "^3.1.0", - "picomatch": "^4.0.7" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/errors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-1.0.0.tgz", - "integrity": "sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/format": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@changesets/format/-/format-0.1.2.tgz", - "integrity": "sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.8.0", - "tinyexec": "^1.3.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/get-dependents-graph": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-3.0.0.tgz", - "integrity": "sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^7.0.0", - "semver": "^7.8.1" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/git": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@changesets/git/-/git-4.0.1.tgz", - "integrity": "sha512-6vWpIAC4LkpmlqaIu37ViT5enyd9+uBwAiGqCGhASvkSHBgx4PrtTGXWTMFYpDmZbjgyonyVgMciPrW4MqtJsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^1.0.0", - "@changesets/types": "^7.0.0", - "@manypkg/get-packages": "^3.1.0", - "picomatch": "^4.0.4", - "tinyexec": "^1.3.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-1.0.0.tgz", - "integrity": "sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^7.0.0", - "yaml": "^2.9.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/pre": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-3.0.0.tgz", - "integrity": "sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^1.0.0", - "@changesets/types": "^7.0.0", - "@manypkg/get-packages": "^3.1.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/read": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-1.0.1.tgz", - "integrity": "sha512-nzvSC6RcTiWf/dsuwZFXpLzGGsRHYCL68U3uHV7srsulU93aVWY7u2GEJiDZ+4GIIElfaW5EqtKC0UHroY+LTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/git": "^4.0.1", - "@changesets/parse": "^1.0.0", - "@changesets/types": "^7.0.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/should-skip-package": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-1.0.0.tgz", - "integrity": "sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^7.0.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/types": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-7.0.0.tgz", - "integrity": "sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@changesets/write": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@changesets/write/-/write-1.0.1.tgz", - "integrity": "sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/format": "^0.1.1", - "@changesets/types": "^7.0.0", - "human-id": "^4.2.0" - }, - "engines": { - "node": "^22.11 || ^24 || >=26" - } - }, - "node_modules/@clack/core": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", - "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-wrap-ansi": "^0.2.0", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 20.12.0" - } - }, - "node_modules/@clack/prompts": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", - "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "1.5.1", - "fast-string-width": "^3.0.2", - "fast-wrap-ansi": "^0.2.0", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 20.12.0" - } - }, - "node_modules/@manypkg/find-root": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-3.1.0.tgz", - "integrity": "sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@manypkg/tools": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@manypkg/get-packages": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-3.1.0.tgz", - "integrity": "sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@manypkg/find-root": "^3.1.0", - "@manypkg/tools": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@manypkg/tools": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@manypkg/tools/-/tools-2.1.2.tgz", - "integrity": "sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "jju": "^1.4.0", - "tinyglobby": "^0.2.13", - "yaml": "^2.9.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@pnpm/deps.graph-sequencer": { - "version": "1100.0.1", - "resolved": "https://registry.npmjs.org/@pnpm/deps.graph-sequencer/-/deps.graph-sequencer-1100.0.1.tgz", - "integrity": "sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.13" - }, - "funding": { - "url": "https://opencollective.com/pnpm" - } - }, - "node_modules/cac": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", - "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/fast-string-truncated-width": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-width": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-truncated-width": "^3.0.2" - } - }, - "node_modules/fast-wrap-ansi": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", - "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-width": "^3.0.2" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/human-id": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.1.tgz", - "integrity": "sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==", - "dev": true, - "license": "MIT", - "bin": { - "human-id": "dist/cli.js" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/jju": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/launch-editor": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", - "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.4" - } - }, - "node_modules/package-manager-detector": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", - "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shell-quote": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.11.0.tgz", - "integrity": "sha512-JdxDPD0DBTyu08pq0kPC0xSNet/qsU07qT6IsX1AS8oO2ICNRY4ldNa8OAI6PuwAH8tG3lxEhbqmyp4Dw4036g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", - "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/yaml": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", - "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - } - } -} diff --git a/package.json b/package.json index 9cb1e5b..8ecac47 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,19 @@ { - "name": "plumb", - "version": "0.1.0", + "name": "plumb-monorepo", "private": true, - "description": "Follow a VM through every layer of OpenStack and OpenSDN, from Keystone to the vRouter, in one tree.", - "license": "UNLICENSED", + "workspaces": [ + "apps/*", + "packages/*" + ], "scripts": { + "build": "turbo run build", + "test": "turbo run test", "changeset": "changeset", "version": "changeset version" }, "devDependencies": { - "@changesets/cli": "^3.0.3" - } + "@changesets/cli": "^3.0.3", + "turbo": "^2.11.5" + }, + "packageManager": "bun@1.4.2" } diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..5548157 --- /dev/null +++ b/turbo.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://turborepo.dev/schema.json", + "futureFlags": { + "experimentalGoWorkspaces": true, + "experimentalTaskCommand": true + }, + "ui": "tui", + "tasks": { + "build": { "dependsOn": ["^build"] }, + "test": {}, + "check": {}, + "fix": { "cache": false }, + "cli#test": { + "command": ["go", "test", "-race", "./..."], + "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/docs/troubleshooting.md"] + }, + "cli#check": { + "command": ["make", "vet"] + }, + "cli#fix": { + "command": ["go", "fmt", "./..."], + "cache": false + } + } +} From 06ffbdc8eaaecdd8de76c2a9b3335c383258551a Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:37:21 +0700 Subject: [PATCH 03/20] build: lint and format with Ultracite Ultracite runs oxlint and oxfmt as root tasks next to the Go check and fix tasks, so turbo run check and turbo run fix cover the whole repo. A husky pre-commit hook fixes staged files before each commit. --- .agents/skills/ultracite/SKILL.md | 125 +++ .../ultracite/references/code-standards.md | 102 ++ .claude/settings.json | 15 + .claude/skills/ultracite | 1 + .github/workflows/ci.yml | 2 +- .husky/pre-commit | 26 + .vscode/settings.json | 62 ++ AGENTS.md | 127 +++ CLAUDE.md | 1 + bun.lock | 871 ++++++++++++++++++ oxfmt.config.ts | 6 + oxlint.config.ts | 13 + package.json | 14 +- skills-lock.json | 11 + turbo.json | 4 +- 15 files changed, 1376 insertions(+), 4 deletions(-) create mode 100644 .agents/skills/ultracite/SKILL.md create mode 100644 .agents/skills/ultracite/references/code-standards.md create mode 100644 .claude/settings.json create mode 120000 .claude/skills/ultracite create mode 100644 .husky/pre-commit create mode 100644 .vscode/settings.json create mode 120000 CLAUDE.md create mode 100644 oxfmt.config.ts create mode 100644 oxlint.config.ts create mode 100644 skills-lock.json diff --git a/.agents/skills/ultracite/SKILL.md b/.agents/skills/ultracite/SKILL.md new file mode 100644 index 0000000..a5b9dcc --- /dev/null +++ b/.agents/skills/ultracite/SKILL.md @@ -0,0 +1,125 @@ +--- +name: ultracite +description: "Ultracite is a zero-config linting and formatting preset for JavaScript/TypeScript projects. Use when: (1) Setting up or initializing Ultracite in a project (ultracite init), (2) Running linting or formatting commands (check, fix, doctor), (3) Writing or reviewing JS/TS code in a project that uses Ultracite — to follow its code standards, (4) Troubleshooting linting/formatting issues, (5) User mentions 'ultracite', 'lint', 'format', 'code quality', or 'biome/eslint/oxlint' in a project with Ultracite installed." +--- + +# Ultracite + +Zero-config linting and formatting for JS/TS projects. Supports three linter backends: **Oxlint** + Oxfmt (recommended), **Biome**, and **ESLint** + Prettier + Stylelint. + +## Detecting Ultracite + +Check if `ultracite` is in `package.json` dependencies or devDependencies. Detect the active linter by looking for (searching upward from the current directory): + +- `biome.json` / `biome.jsonc` → Biome +- `eslint.config.*` (`.mjs`, `.js`, `.cjs`, `.ts`, `.mts`, `.cts`) → ESLint (with Prettier for formatting) +- `oxlint.config.ts` → Oxlint (with `oxfmt.config.ts` for formatting) + +## CLI Commands + +```bash +# Check for issues (read-only) +bunx ultracite check + +# Auto-fix issues +bunx ultracite fix + +# Diagnose setup problems +bunx ultracite doctor + +# Initialize in a new project +bunx ultracite init +``` + +Replace `bunx` with `npx`, `pnpm exec`, or `yarn` depending on the package manager, so the project's installed Ultracite runs (`pnpm dlx` and `yarn dlx` download the latest release instead, and Yarn 1 has no `dlx`). Only `init` in a project without Ultracite needs a download runner such as `pnpm dlx`. + +`check` and `fix` accept optional file paths: `bunx ultracite check src/index.ts`. Unknown options are passed through to the underlying linter (e.g. `bunx ultracite check --max-warnings 0`). + +## Initialization + +`bunx ultracite init` runs an interactive setup. For non-interactive (CI) use, pass flags: + +```bash +bunx ultracite init \ + --pm bun \ + --linter biome \ + --editors universal \ + --agents claude copilot \ + --frameworks react next \ + --integrations husky lint-staged \ + --quiet +``` + +**Flags:** + +- `--pm` — `npm` | `yarn` | `pnpm` | `bun` | `deno` | `nub` | `aube` +- `--linter` — `oxlint` (recommended) | `biome` | `eslint` +- `--editors` — `universal` (writes `.vscode/settings.json` for every VS Code-based editor) | `vscode` | `cursor` | `windsurf` | `codebuddy` | `antigravity` | `bob` | `kiro` | `trae` | `void` | `zed` +- `--agents` — `universal` (writes `AGENTS.md`) | `claude` | `codex` | `copilot` | `cline` | `amp` | `gemini` | `cursor-cli` + 34 more (41 agents supported) +- `--frameworks` — `react` | `next` | `solid` | `vue` | `svelte` | `qwik` | `remix` | `tanstack` | `angular` | `astro` | `nestjs` | `jest` | `vitest` +- `--integrations` — `husky` | `lefthook` | `lint-staged` | `pre-commit` +- `--hooks` — Enable auto-fix hooks: `claude` | `copilot` | `cursor` | `windsurf` | `codebuddy` +- `--js-plugins` — Opt-in Oxlint JS plugins: `@shadcn/lint` | `anti-slop` | `eslint-plugin-github` | `eslint-plugin-sonarjs` | `oxlint-plugin-react-doctor` +- `--type-aware` — Enable type-aware linting (Biome: extends the `type-aware` preset; Oxlint: installs `oxlint-tsgolint`) +- `--install-skill` — Install the reusable Ultracite skill after setup +- `--skip-install` — Skip dependency installation +- `--quiet` — Suppress prompts (auto-detected when `CI=true`); defaults to `oxlint` when `--linter` is omitted + +Init creates config that extends Ultracite presets: + +```jsonc +// biome.jsonc +{ "extends": ["ultracite/biome/core", "ultracite/biome/react"] } +``` + +```ts +// eslint.config.mjs — arrays of flat configs, spread together +import core from "ultracite/eslint/core"; +import react from "ultracite/eslint/react"; +export default [...core, ...react]; +``` + +```ts +// oxlint.config.ts — imports passed to extends +import { defineConfig } from "oxlint"; +import core from "ultracite/oxlint/core"; +export default defineConfig({ + extends: [core], + ignorePatterns: core.ignorePatterns, +}); +``` + +Presets available per linter (`ultracite//`): `core`, `react`, `next`, `solid`, `vue`, `svelte`, `qwik`, `remix`, `tanstack`, `angular`, `astro`, `nestjs`, `jest`, `vitest`. Biome also has `type-aware`. Oxlint also has `js-plugins` (`eslint-plugin-github`, `eslint-plugin-sonarjs` and `oxlint-plugin-react-doctor` run through oxlint's JS plugin support, with `next/js-plugins` and `tanstack/js-plugins` adding framework-specific React Doctor rules), `anti-slop` and `shadcn`. These are opt-in: init only adds them when picked in its prompt or passed with `--js-plugins`. + +## Code Standards + +When writing code in a project with Ultracite, follow these standards. For the full rules reference, see [references/code-standards.md](references/code-standards.md). + +Key rules at a glance: + +Formatting is handled by the project's configured linter/formatter. Respect the repository's existing formatter settings instead of forcing one fixed line width, quote style, or trailing comma policy. + +**Type safety:** Use explicit types when they improve clarity. Prefer `unknown` over `any`. Use `as const` for immutable values and rely on type narrowing over blunt assertions. + +**Modern JavaScript/TypeScript:** Prefer `const`, destructuring, optional chaining, nullish coalescing, template literals, `for...of`, and concise arrow functions. + +**Async and correctness:** Always `await` promises in async functions. Prefer `async/await` over promise chains. Remove `console.log`, `debugger`, and `alert` from production code. + +**React and accessibility:** Use function components, keep hooks top-level with correct deps, avoid nested component definitions, and use semantic HTML with the right labels, headings, alt text, and keyboard affordances. + +**Organization, security, performance, and testing:** Keep functions focused, prefer early returns, avoid `dangerouslySetInnerHTML` and `eval()`, prefer specific imports and top-level regex, and keep tests free of `.only` and `.skip`. + +## Troubleshooting + +Run `bunx ultracite doctor` to diagnose. It checks: + +1. Linter and formatter installation (Biome; or ESLint + Prettier + Stylelint; or Oxlint + oxfmt) +2. Config validity (extends the ultracite presets correctly) +3. Ultracite in package.json dependencies +4. Conflicting tools (legacy `.eslintrc.*` files; `.prettierrc.*`/`prettier.config.*` when not using the ESLint backend) + +Common fixes: + +- **Conflicting configs**: Delete legacy `.eslintrc.*` and `.prettierrc.*` files after migrating to Ultracite +- **Missing dependency**: Run `bunx ultracite init` again or manually add `ultracite` to devDependencies +- **Rules not applying**: Ensure config file extends the correct presets for your framework diff --git a/.agents/skills/ultracite/references/code-standards.md b/.agents/skills/ultracite/references/code-standards.md new file mode 100644 index 0000000..aecda36 --- /dev/null +++ b/.agents/skills/ultracite/references/code-standards.md @@ -0,0 +1,102 @@ +# Ultracite Code Standards + +Formatting is intentionally handled by your project's configured linter or formatter. Respect the repository's existing quote, width, semicolon, trailing comma, and line ending settings instead of forcing one global formatting style. + +## Core Principles + +Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity. + +## Type Safety & Explicitness + +- Use explicit types for function parameters and return values when they enhance clarity +- Prefer `unknown` over `any` when the type is genuinely unknown +- Use const assertions (`as const`) for immutable values and literal types +- Leverage TypeScript's type narrowing instead of type assertions +- Use meaningful variable names instead of magic numbers — extract constants with descriptive names + +## Modern JavaScript/TypeScript + +- Use arrow functions for callbacks and short functions +- Prefer `for...of` loops over `.forEach()` and indexed `for` loops +- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access +- Prefer template literals over string concatenation +- Use destructuring for object and array assignments +- Use `const` by default, `let` only when reassignment is needed, never `var` + +## Async & Promises + +- Always `await` promises in async functions — don't forget to use the return value +- Use `async/await` syntax instead of promise chains for better readability +- Handle errors appropriately in async code with try-catch blocks +- Don't use async functions as Promise executors + +## React & JSX + +- Use function components over class components +- Call hooks at the top level only, never conditionally +- Specify all dependencies in hook dependency arrays correctly +- Use the `key` prop for elements in iterables (prefer unique IDs over array indices) +- Nest children between opening and closing tags instead of passing as props +- Don't define components inside other components +- Use semantic HTML and ARIA attributes for accessibility: + - Provide meaningful alt text for images + - Use proper heading hierarchy + - Add labels for form inputs + - Include keyboard event handlers alongside mouse events + - Use semantic elements (`