diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index dc5f758..ae04bb4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -40,4 +40,4 @@ jobs:
- name: Package all supported platforms
working-directory: plugin
- run: make package
+ run: make package verify-package
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0cd0fd1..7747410 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -182,7 +182,7 @@ jobs:
- name: Package all platforms
working-directory: plugin
- run: make package
+ run: make package verify-package
- name: Extract checksums
working-directory: plugin
diff --git a/Makefile b/Makefile
index adc7d24..240205c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,10 @@
-.PHONY: build test fmt vet package package-host clean
+.PHONY: build test fmt vet package package-host verify-package verify-package-host clean
BIN := bin/kandev-plugin-github-status
VERSION := 0.1.1
STAGE := .build/stage
PKG_OUT := kandev-plugin-github-status-$(VERSION).tar.gz
+KANDEV_BACKEND := ../kandev/apps/backend
build:
mkdir -p bin
@@ -18,18 +19,20 @@ fmt:
vet:
go vet ./server/...
-## Cross-compile every platform in manifest.yaml, stage manifest + ui, pack.
+## Cross-compile every platform in manifest.yaml, stage manifest + assets + ui,
+## then pack the archive.
package:
rm -rf $(STAGE)
mkdir -p $(STAGE)/server
cp manifest.yaml $(STAGE)/manifest.yaml
+ cp -r assets $(STAGE)/assets
cp -r ui $(STAGE)/ui
GOOS=linux GOARCH=amd64 go build -o $(STAGE)/server/plugin-linux-amd64 ./server
GOOS=linux GOARCH=arm64 go build -o $(STAGE)/server/plugin-linux-arm64 ./server
GOOS=darwin GOARCH=amd64 go build -o $(STAGE)/server/plugin-darwin-amd64 ./server
GOOS=darwin GOARCH=arm64 go build -o $(STAGE)/server/plugin-darwin-arm64 ./server
GOOS=windows GOARCH=amd64 go build -o $(STAGE)/server/plugin-windows-amd64.exe ./server
- go run github.com/kandev/kandev/cmd/plugin-pack -dir $(STAGE) -out $(PKG_OUT)
+ go -C $(KANDEV_BACKEND) run ./cmd/plugin-pack -dir $(abspath $(STAGE)) -out $(abspath $(PKG_OUT))
rm -rf $(STAGE)
@echo "Wrote $(PKG_OUT)"
@@ -38,11 +41,42 @@ package-host:
rm -rf $(STAGE)
mkdir -p $(STAGE)/server
cp manifest.yaml $(STAGE)/manifest.yaml
+ cp -r assets $(STAGE)/assets
cp -r ui $(STAGE)/ui
go build -o $(STAGE)/server/plugin-$$(go env GOOS)-$$(go env GOARCH)$$(go env GOEXE) ./server
- go run github.com/kandev/kandev/cmd/plugin-pack -dir $(STAGE) -out $(PKG_OUT) -platform-only
+ go -C $(KANDEV_BACKEND) run ./cmd/plugin-pack -dir $(abspath $(STAGE)) -out $(abspath $(PKG_OUT)) -platform-only
rm -rf $(STAGE)
@echo "Wrote $(PKG_OUT)"
+## Verify the generated archive, including the manifest-declared marketplace
+## icon and plugin-pack's checksums, before it reaches the host installer.
+define verify_package_archive
+set -eu; \
+VERIFY_DIR="$$(mktemp -d)"; \
+trap 'rm -rf "$$VERIFY_DIR"' EXIT; \
+test -f "$(PKG_OUT)" || { echo "package not found: $(PKG_OUT)"; exit 1; }; \
+tar -xzf "$(PKG_OUT)" -C "$$VERIFY_DIR"; \
+test -f "$$VERIFY_DIR/manifest.yaml"; \
+grep -Fx 'icon: "assets/icon.svg"' "$$VERIFY_DIR/manifest.yaml" >/dev/null; \
+test -f "$$VERIFY_DIR/assets/icon.svg"; \
+test -f "$$VERIFY_DIR/assets/NOTICE.md"; \
+test -f "$$VERIFY_DIR/ui/bundle.js"; \
+test -f "$$VERIFY_DIR/ui/plugin.css"; \
+test -f "$$VERIFY_DIR/checksums.txt"; \
+grep -Eq '^[0-9a-f]{64} assets/icon\.svg$$' "$$VERIFY_DIR/checksums.txt"; \
+if command -v sha256sum >/dev/null 2>&1; then \
+ (cd "$$VERIFY_DIR" && sha256sum -c checksums.txt); \
+else \
+ (cd "$$VERIFY_DIR" && shasum -a 256 -c checksums.txt); \
+fi; \
+$(1)
+endef
+
+verify-package:
+ @$(call verify_package_archive,for executable in server/plugin-linux-amd64 server/plugin-linux-arm64 server/plugin-darwin-amd64 server/plugin-darwin-arm64 server/plugin-windows-amd64.exe; do test -f "$$VERIFY_DIR/$$executable" || { echo "package missing $$executable"; exit 1; }; done)
+
+verify-package-host:
+ @$(call verify_package_archive,test -f "$$VERIFY_DIR/server/plugin-$$(go env GOOS)-$$(go env GOARCH)$$(go env GOEXE)" || { echo "package missing host executable"; exit 1; })
+
clean:
rm -rf bin $(STAGE) kandev-plugin-github-status-*.tar.gz
diff --git a/README.md b/README.md
index 9dd4edb..3241a81 100644
--- a/README.md
+++ b/README.md
@@ -203,15 +203,15 @@ Built against a sibling checkout of the kandev monorepo — `go.mod` has
expects `../kandev` next to it.
```bash
-go test ./server/... # 59 test functions, 79 runs incl. subtests
+go test ./server/... # 60 test functions, 80 runs incl. subtests
go vet ./server/...
gofmt -l .
-make package-host # host platform only, fast local loop
-make package # all five platforms in manifest.yaml
+make package-host verify-package-host # host platform only, fast local loop
+make package verify-package # all five manifest platforms
```
CI (`.github/workflows/ci.yml`) runs tidy + gofmt + vet + test on every PR,
-and `build.yml` packages all five platforms. Both check out `kdlbs/kandev`
+and `build.yml` packages and verifies all five platforms. Both check out `kdlbs/kandev`
as a sibling so the `replace` in `go.mod` resolves. `release.yml` is manual
(`workflow_dispatch` with a patch/minor/major bump, or a `v*` tag push): it
syncs `manifest.yaml`/`Makefile`/README, writes `CHANGELOG.md`, tags, then
diff --git a/assets/NOTICE.md b/assets/NOTICE.md
new file mode 100644
index 0000000..068a381
--- /dev/null
+++ b/assets/NOTICE.md
@@ -0,0 +1,10 @@
+# Marketplace icon notice
+
+`icon.svg` is an original Kandev integration asset. Its generic service-health
+pulse motif does not reproduce or incorporate GitHub, Invertocat, or Octocat
+logo artwork.
+
+GITHUB, the GITHUB logo design, INVERTOCAT, OCTOCAT, and the OCTOCAT logo design
+are trademarks of GitHub, Inc., registered in the United States and other
+countries. This plugin is not affiliated with, sponsored by, or endorsed by
+GitHub.
diff --git a/assets/icon.svg b/assets/icon.svg
new file mode 100644
index 0000000..e3be330
--- /dev/null
+++ b/assets/icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/manifest.yaml b/manifest.yaml
index 642a1ed..97d1380 100644
--- a/manifest.yaml
+++ b/manifest.yaml
@@ -6,6 +6,7 @@ description: "Is it me or is it GitHub? A quiet status-bar chip that turns loud
author: "kandev"
categories: ["tools"]
repo_url: "https://github.com/kdlbs/kandev-plugin-github-status"
+icon: "assets/icon.svg"
runtime:
type: binary
diff --git a/server/manifest_test.go b/server/manifest_test.go
new file mode 100644
index 0000000..943ea5c
--- /dev/null
+++ b/server/manifest_test.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "encoding/xml"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestManifestIncludesPackagedMarketplaceIcon(t *testing.T) {
+ contents, err := os.ReadFile("../manifest.yaml")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ iconPath := manifestIconPath(string(contents))
+ if iconPath != "assets/icon.svg" {
+ t.Fatalf("manifest icon = %q, want %q", iconPath, "assets/icon.svg")
+ }
+
+ icon, err := os.ReadFile(filepath.Join("..", filepath.FromSlash(iconPath)))
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertMarketplaceSVG(t, icon)
+}
+
+func manifestIconPath(manifest string) string {
+ for _, line := range strings.Split(manifest, "\n") {
+ if strings.HasPrefix(line, "icon:") {
+ return strings.Trim(strings.TrimSpace(strings.TrimPrefix(line, "icon:")), `"`)
+ }
+ }
+ return ""
+}
+
+func assertMarketplaceSVG(t *testing.T, icon []byte) {
+ t.Helper()
+
+ var root struct {
+ XMLName xml.Name
+ Width string `xml:"width,attr"`
+ Height string `xml:"height,attr"`
+ ViewBox string `xml:"viewBox,attr"`
+ }
+ if err := xml.Unmarshal(icon, &root); err != nil {
+ t.Fatal(err)
+ }
+ if root.XMLName.Local != "svg" || root.Width != "128" || root.Height != "128" || root.ViewBox != "0 0 128 128" {
+ t.Fatalf("unexpected SVG root: name=%q width=%q height=%q viewBox=%q", root.XMLName.Local, root.Width, root.Height, root.ViewBox)
+ }
+
+ lower := strings.ToLower(string(icon))
+ for _, forbidden := range []string{"