From a69dde35ae732febc044db3c78d903606da86781 Mon Sep 17 00:00:00 2001 From: Felipe Martin Date: Thu, 14 Aug 2025 16:36:55 +0200 Subject: [PATCH 1/2] refactor: pluginctl v0.1.5-0.20250814142442-19c417d53e1d --- .editorconfig | 5 +- .github/workflows/ci.yml | 3 + .golangci.yml | 107 +++++++---- .nvmrc | 2 +- Makefile | 348 +--------------------------------- build/{setup.mk => _setup.mk} | 19 +- build/build.mk | 90 +++++++++ build/deploy.mk | 52 +++++ build/dev.mk | 53 ++++++ build/manifest/main.go | 76 -------- build/pluginctl/main.go | 180 ------------------ build/test.mk | 59 ++++++ build/utils.mk | 42 ++++ build/versioning.mk | 111 +++++++++++ plugin.json | 321 +++++++++++++++++-------------- 15 files changed, 676 insertions(+), 792 deletions(-) rename build/{setup.mk => _setup.mk} (64%) create mode 100644 build/build.mk create mode 100644 build/deploy.mk create mode 100644 build/dev.mk delete mode 100644 build/manifest/main.go delete mode 100644 build/pluginctl/main.go create mode 100644 build/test.mk create mode 100644 build/utils.mk create mode 100644 build/versioning.mk diff --git a/.editorconfig b/.editorconfig index d3188bb5..bd669a83 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,18 +11,17 @@ trim_trailing_whitespace = true [*.go] indent_style = tab -[*.{js,jsx,json,html}] +[*.{js, jsx, ts, tsx, json, html}] indent_style = space indent_size = 4 [webapp/package.json] indent_size = 2 -[Makefile,*.mk] +[{Makefile, *.mk}] indent_style = tab [*.md] indent_style = space indent_size = 4 trim_trailing_whitespace = false - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c05ae63..5ce03e6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,11 @@ on: permissions: contents: read + id-token: write jobs: plugin-ci: uses: mattermost/actions-workflows/.github/workflows/plugin-ci.yml@main secrets: inherit + with: + golang-version: "1.24" diff --git a/.golangci.yml b/.golangci.yml index 4de504a8..8982e56b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,52 +1,87 @@ -run: - timeout: 5m - modules-download-mode: readonly - -linters-settings: - gofmt: - simplify: true - goimports: - local-prefixes: github.com/mattermost/mattermost-plugin-demo - govet: - check-shadowing: true - enable-all: true - disable: - - fieldalignment - misspell: - locale: US - revive: - rules: - - name: unused-parameter - disabled: true +version: "2" linters: - disable-all: true enable: - bodyclose - errcheck - gocritic - - gofmt - - goimports - gosec - - gosimple - - govet - ineffassign - misspell - nakedret - revive - - staticcheck - - stylecheck - - typecheck + - staticcheck # Now includes gosimple and stylecheck - unconvert - unused - whitespace + - govet # Ensure this is included + + settings: + errcheck: + # Add any errcheck settings here + exclude-functions: + - io.Copy(*bytes.Buffer) + + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + + gosec: + # Add gosec settings + excludes: + - G104 # Errors unhandled + + staticcheck: + # Configure staticcheck (includes gosimple/stylecheck checks) + checks: ["all"] + + revive: + # Add revive rules + rules: + - name: exported + disabled: false + + exclusions: + presets: + - comments + - std-error-handling + - common-false-positives + + rules: + - path: '_test\.go' + linters: + - errcheck + - gosec + +formatters: + enable: + - gofmt + - goimports + + settings: + gofmt: + simplify: true + + goimports: + local-prefixes: + - github.com/mattermost/mattermost-plugin-demo + +output: + formats: + text: + path: stdout + colors: true + print-linter-name: true + +run: + timeout: 5m + tests: true issues: - exclude-rules: - - path: server/configuration.go - linters: - - unused - - path: _test\.go - linters: - - bodyclose - - scopelint # https://github.com/kyoh86/scopelint/issues/4 + max-issues-per-linter: 0 + max-same-issues: 0 + fix: false diff --git a/.nvmrc b/.nvmrc index a58d2d2c..a3597ecb 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.18.2 \ No newline at end of file +20.11 diff --git a/Makefile b/Makefile index f71fd997..ac014a27 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,6 @@ GO ?= $(shell command -v go 2> /dev/null) NPM ?= $(shell command -v npm 2> /dev/null) CURL ?= $(shell command -v curl 2> /dev/null) MM_DEBUG ?= -MANIFEST_FILE ?= plugin.json GOPATH ?= $(shell go env GOPATH) GO_TEST_FLAGS ?= -race GO_BUILD_FLAGS ?= @@ -13,6 +12,10 @@ DEFAULT_GOARCH := $(shell go env GOARCH) export GO111MODULE=on +# We need to export GOBIN to allow it to be set +# for processes spawned from the Makefile +export GOBIN ?= $(PWD)/bin + # You can include assets this directory into the bundle. This can be e.g. used to include profile pictures. ASSETS_DIR ?= assets @@ -20,350 +23,11 @@ ASSETS_DIR ?= assets .PHONY: default default: all -# Verify environment, and define PLUGIN_ID, PLUGIN_VERSION, HAS_SERVER and HAS_WEBAPP as needed. -include build/setup.mk -include build/legacy.mk - -BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz - -# Include custom makefile, if present -ifneq ($(wildcard build/custom.mk),) - include build/custom.mk -endif - ifneq ($(MM_DEBUG),) GO_BUILD_GCFLAGS = -gcflags "all=-N -l" else GO_BUILD_GCFLAGS = endif - - -# ==================================================================================== -# Used for semver bumping -PROTECTED_BRANCH := master -APP_NAME := $(shell basename -s .git `git config --get remote.origin.url`) -CURRENT_VERSION := $(shell git describe --abbrev=0 --tags) -VERSION_PARTS := $(subst ., ,$(subst v,,$(subst -rc, ,$(CURRENT_VERSION)))) -MAJOR := $(word 1,$(VERSION_PARTS)) -MINOR := $(word 2,$(VERSION_PARTS)) -PATCH := $(word 3,$(VERSION_PARTS)) -RC := $(shell echo $(CURRENT_VERSION) | grep -oE 'rc[0-9]+' | sed 's/rc//') -# Check if current branch is protected -define check_protected_branch - @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ - if ! echo "$(PROTECTED_BRANCH)" | grep -wq "$$current_branch" && ! echo "$$current_branch" | grep -q "^release"; then \ - echo "Error: Tagging is only allowed from $(PROTECTED_BRANCH) or release branches. You are on $$current_branch branch."; \ - exit 1; \ - fi -endef -# Check if there are pending pulls -define check_pending_pulls - @git fetch; \ - current_branch=$$(git rev-parse --abbrev-ref HEAD); \ - if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/$$current_branch)" ]; then \ - echo "Error: Your branch is not up to date with upstream. Please pull the latest changes before performing a release"; \ - exit 1; \ - fi -endef -# Prompt for approval -define prompt_approval - @read -p "About to bump $(APP_NAME) to version $(1), approve? (y/n) " userinput; \ - if [ "$$userinput" != "y" ]; then \ - echo "Bump aborted."; \ - exit 1; \ - fi -endef -# ==================================================================================== - -.PHONY: patch minor major patch-rc minor-rc major-rc - -patch: ## to bump patch version (semver) - $(call check_protected_branch) - $(call check_pending_pulls) - @$(eval PATCH := $(shell echo $$(($(PATCH)+1)))) - $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) - @echo Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) - git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)" - git push origin v$(MAJOR).$(MINOR).$(PATCH) - @echo Bumped $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) - -minor: ## to bump minor version (semver) - $(call check_protected_branch) - $(call check_pending_pulls) - @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) - @$(eval PATCH := 0) - $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) - @echo Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) - git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)" - git push origin v$(MAJOR).$(MINOR).$(PATCH) - @echo Bumped $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) - -major: ## to bump major version (semver) - $(call check_protected_branch) - $(call check_pending_pulls) - $(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) - $(eval MINOR := 0) - $(eval PATCH := 0) - $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) - @echo Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) - git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)" - git push origin v$(MAJOR).$(MINOR).$(PATCH) - @echo Bumped $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) - -patch-rc: ## to bump patch release candidate version (semver) - $(call check_protected_branch) - $(call check_pending_pulls) - @$(eval RC := $(shell echo $$(($(RC)+1)))) - $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) - @echo Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" - git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - @echo Bumped $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - -minor-rc: ## to bump minor release candidate version (semver) - $(call check_protected_branch) - $(call check_pending_pulls) - @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) - @$(eval PATCH := 0) - @$(eval RC := 1) - $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) - @echo Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" - git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - @echo Bumped $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - -major-rc: ## to bump major release candidate version (semver) - $(call check_protected_branch) - $(call check_pending_pulls) - @$(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) - @$(eval MINOR := 0) - @$(eval PATCH := 0) - @$(eval RC := 1) - $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) - @echo Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" - git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - @echo Bumped $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) - -## Checks the code style, tests, builds and bundles the plugin. -.PHONY: all -all: check-style test dist - -## Runs eslint and golangci-lint -.PHONY: check-style -check-style: webapp/node_modules - @echo Checking for style guide compliance - -ifneq ($(HAS_WEBAPP),) - cd webapp && npm run lint - cd webapp && npm run check-types -endif - -ifneq ($(HAS_SERVER),) - @if ! [ -x "$$(command -v golangci-lint)" ]; then \ - echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \ - exit 1; \ - fi; \ - - @echo Running golangci-lint - golangci-lint run ./... -endif - -## Builds the server, if it exists, for all supported architectures, unless MM_SERVICESETTINGS_ENABLEDEVELOPER is set. -.PHONY: server -server: -ifneq ($(HAS_SERVER),) -ifneq ($(MM_DEBUG),) - $(info DEBUG mode is on; to disable, unset MM_DEBUG) -endif - mkdir -p server/dist; -ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),) - @echo Building plugin only for $(DEFAULT_GOOS)-$(DEFAULT_GOARCH) because MM_SERVICESETTINGS_ENABLEDEVELOPER is enabled - cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH); -else - cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-amd64; - cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-arm64; - cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-amd64; - cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-arm64; - cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-windows-amd64.exe; -endif -endif - -## Ensures NPM dependencies are installed without having to run this all the time. -webapp/node_modules: $(wildcard webapp/package.json) -ifneq ($(HAS_WEBAPP),) - cd webapp && $(NPM) install - touch $@ -endif - -## Builds the webapp, if it exists. -.PHONY: webapp -webapp: webapp/node_modules -ifneq ($(HAS_WEBAPP),) -ifeq ($(MM_DEBUG),) - cd webapp && $(NPM) run build; -else - cd webapp && $(NPM) run debug; -endif -endif - -## Generates a tar bundle of the plugin for install. -.PHONY: bundle -bundle: - rm -rf dist/ - mkdir -p dist/$(PLUGIN_ID) - cp $(MANIFEST_FILE) dist/$(PLUGIN_ID)/ -ifneq ($(wildcard $(ASSETS_DIR)/.),) - cp -r $(ASSETS_DIR) dist/$(PLUGIN_ID)/ -endif -ifneq ($(HAS_PUBLIC),) - cp -r public dist/$(PLUGIN_ID)/ -endif -ifneq ($(HAS_SERVER),) - mkdir -p dist/$(PLUGIN_ID)/server - cp -r server/dist dist/$(PLUGIN_ID)/server/ -endif -ifneq ($(HAS_WEBAPP),) - mkdir -p dist/$(PLUGIN_ID)/webapp - cp -r webapp/dist dist/$(PLUGIN_ID)/webapp/ -endif - cd dist && tar -cvzf $(BUNDLE_NAME) $(PLUGIN_ID) - - @echo plugin built at: dist/$(BUNDLE_NAME) - -## Builds and bundles the plugin. -.PHONY: dist -dist: server webapp bundle - -## Builds and installs the plugin to a server. -.PHONY: deploy -deploy: dist - ./build/bin/pluginctl deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME) - -## Builds and installs the plugin to a server, updating the webapp automatically when changed. -.PHONY: watch -watch: server bundle -ifeq ($(MM_DEBUG),) - cd webapp && $(NPM) run build:watch -else - cd webapp && $(NPM) run debug:watch -endif - -## Installs a previous built plugin with updated webpack assets to a server. -.PHONY: deploy-from-watch -deploy-from-watch: bundle - ./build/bin/pluginctl deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME) - -## Setup dlv for attaching, identifying the plugin PID for other targets. -.PHONY: setup-attach -setup-attach: - $(eval PLUGIN_PID := $(shell ps aux | grep "plugins/${PLUGIN_ID}" | grep -v "grep" | awk -F " " '{print $$2}')) - $(eval NUM_PID := $(shell echo -n ${PLUGIN_PID} | wc -w)) - - @if [ ${NUM_PID} -gt 2 ]; then \ - echo "** There is more than 1 plugin process running. Run 'make kill reset' to restart just one."; \ - exit 1; \ - fi - -## Check if setup-attach succeeded. -.PHONY: check-attach -check-attach: - @if [ -z ${PLUGIN_PID} ]; then \ - echo "Could not find plugin PID; the plugin is not running. Exiting."; \ - exit 1; \ - else \ - echo "Located Plugin running with PID: ${PLUGIN_PID}"; \ - fi - -## Attach dlv to an existing plugin instance. -.PHONY: attach -attach: setup-attach check-attach - dlv attach ${PLUGIN_PID} - -## Attach dlv to an existing plugin instance, exposing a headless instance on $DLV_DEBUG_PORT. -.PHONY: attach-headless -attach-headless: setup-attach check-attach - dlv attach ${PLUGIN_PID} --listen :$(DLV_DEBUG_PORT) --headless=true --api-version=2 --accept-multiclient - -## Detach dlv from an existing plugin instance, if previously attached. -.PHONY: detach -detach: setup-attach - @DELVE_PID=$(shell ps aux | grep "dlv attach ${PLUGIN_PID}" | grep -v "grep" | awk -F " " '{print $$2}') && \ - if [ "$$DELVE_PID" -gt 0 ] > /dev/null 2>&1 ; then \ - echo "Located existing delve process running with PID: $$DELVE_PID. Killing." ; \ - kill -9 $$DELVE_PID ; \ - fi - -## Runs any lints and unit tests defined for the server and webapp, if they exist. -.PHONY: test -test: webapp/node_modules -ifneq ($(HAS_SERVER),) - $(GO) test -v $(GO_TEST_FLAGS) ./server/... -endif -ifneq ($(HAS_WEBAPP),) - cd webapp && $(NPM) run test; -endif - -## Creates a coverage report for the server code. -.PHONY: coverage -coverage: webapp/node_modules -ifneq ($(HAS_SERVER),) - $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... - $(GO) tool cover -html=server/coverage.txt -endif - -## Extract strings for translation from the source code. -.PHONY: i18n-extract -i18n-extract: -ifneq ($(HAS_WEBAPP),) -ifeq ($(HAS_MM_UTILITIES),) - @echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command" -else - cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir $(PWD)/webapp -endif -endif - -## Disable the plugin. -.PHONY: disable -disable: detach - ./build/bin/pluginctl disable $(PLUGIN_ID) - -## Enable the plugin. -.PHONY: enable -enable: - ./build/bin/pluginctl enable $(PLUGIN_ID) - -## Reset the plugin, effectively disabling and re-enabling it on the server. -.PHONY: reset -reset: detach - ./build/bin/pluginctl reset $(PLUGIN_ID) - -## Kill all instances of the plugin, detaching any existing dlv instance. -.PHONY: kill -kill: detach - $(eval PLUGIN_PID := $(shell ps aux | grep "plugins/${PLUGIN_ID}" | grep -v "grep" | awk -F " " '{print $$2}')) - - @for PID in ${PLUGIN_PID}; do \ - echo "Killing plugin pid $$PID"; \ - kill -9 $$PID; \ - done; \ - -## Clean removes all build artifacts. -.PHONY: clean -clean: - rm -fr dist/ -ifneq ($(HAS_SERVER),) - rm -fr server/coverage.txt - rm -fr server/dist -endif -ifneq ($(HAS_WEBAPP),) - rm -fr webapp/junit.xml - rm -fr webapp/dist - rm -fr webapp/node_modules -endif - rm -fr build/bin/ - -# Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html -help: - @cat Makefile build/*.mk | grep -v '\.PHONY' | grep -v '\help:' | grep -B1 -E '^[a-zA-Z0-9_.-]+:.*' | sed -e "s/:.*//" | sed -e "s/^## //" | grep -v '\-\-' | sed '1!G;h;$$!d' | awk 'NR%2{printf "\033[36m%-30s\033[0m",$$0;next;}1' | sort +# Include modular makefiles +include build/*.mk diff --git a/build/setup.mk b/build/_setup.mk similarity index 64% rename from build/setup.mk rename to build/_setup.mk index 493b06fc..e3f6a507 100644 --- a/build/setup.mk +++ b/build/_setup.mk @@ -4,29 +4,28 @@ ifeq ($(GO),) $(error "go is not available: see https://golang.org/doc/install") endif -# Ensure that the build tools are compiled. Go's caching makes this quick. -$(shell cd build/manifest && $(GO) build -o ../bin/manifest) - -# Ensure that the deployment tools are compiled. Go's caching makes this quick. -$(shell cd build/pluginctl && $(GO) build -o ../bin/pluginctl) +# Gather build variables to inject into the manifest tool +BUILD_HASH_SHORT = $(shell git rev-parse --short HEAD 2>/dev/null) +BUILD_TAG_LATEST = $(shell git describe --tags --match 'v*' --abbrev=0 2>/dev/null) +BUILD_TAG_CURRENT = $(shell git tag --points-at HEAD 2>/dev/null) # Extract the plugin id from the manifest. -PLUGIN_ID ?= $(shell build/bin/manifest id) +PLUGIN_ID ?= $(shell pluginctl manifest get '{{.Id}}') ifeq ($(PLUGIN_ID),) $(error "Cannot parse id from $(MANIFEST_FILE)") endif # Extract the plugin version from the manifest. -PLUGIN_VERSION ?= $(shell build/bin/manifest version) +PLUGIN_VERSION ?= $(shell pluginctl manifest get '{{.Version}}') ifeq ($(PLUGIN_VERSION),) $(error "Cannot parse version from $(MANIFEST_FILE)") endif # Determine if a server is defined in the manifest. -HAS_SERVER ?= $(shell build/bin/manifest has_server) +HAS_SERVER ?= $(shell pluginctl manifest get '{{.HasServer}}') # Determine if a webapp is defined in the manifest. -HAS_WEBAPP ?= $(shell build/bin/manifest has_webapp) +HAS_WEBAPP ?= $(shell pluginctl manifest get '{{.HasWebapp}}') # Determine if a /public folder is in use HAS_PUBLIC ?= $(wildcard public/.) @@ -43,3 +42,5 @@ ifeq ($(NPM),) $(error "npm is not available: see https://www.npmjs.com/get-npm") endif endif + +BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz diff --git a/build/build.mk b/build/build.mk new file mode 100644 index 00000000..22cec48f --- /dev/null +++ b/build/build.mk @@ -0,0 +1,90 @@ +# ==================================================================================== +# Build Targets +# ==================================================================================== + +## Checks the code style, tests, builds and bundles the plugin. +.PHONY: all +all: check-style test dist + +## Ensures the plugin manifest is valid +.PHONY: manifest-check +manifest-check: + pluginctl manifest check + +## Cleans the server build artifacts. +.PHONY: clean-server +clean-server: +ifneq ($(HAS_SERVER),) + rm -rf server/dist +endif + +## Builds the server, if it exists, for all supported architectures, unless MM_SERVICESETTINGS_ENABLEDEVELOPER is set. +.PHONY: server +server: clean-server +server: +ifneq ($(HAS_SERVER),) +ifneq ($(MM_DEBUG),) + $(info DEBUG mode is on; to disable, unset MM_DEBUG) +endif + mkdir -p server/dist; +ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),) + @echo Building plugin only for $(DEFAULT_GOOS)-$(DEFAULT_GOARCH) because MM_SERVICESETTINGS_ENABLEDEVELOPER is enabled + cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH); +else + cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-amd64; + cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-arm64; + cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-amd64; + cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-arm64; + cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-windows-amd64.exe; +endif +endif + +## Ensures NPM dependencies are installed without having to run this all the time. +webapp/node_modules: $(wildcard webapp/package.json) +ifneq ($(HAS_WEBAPP),) + cd webapp && $(NPM) install + touch $@ +endif + +## Builds the webapp, if it exists. +.PHONY: webapp +webapp: webapp/node_modules +ifneq ($(HAS_WEBAPP),) +ifeq ($(MM_DEBUG),) + cd webapp && $(NPM) run build; +else + cd webapp && $(NPM) run debug; +endif +endif + +## Generates a tar bundle of the plugin for install. +.PHONY: bundle +bundle: + rm -rf dist/ + mkdir -p dist/$(PLUGIN_ID) + cp plugin.json dist/$(PLUGIN_ID)/plugin.json +ifneq ($(wildcard $(ASSETS_DIR)/.),) + cp -r $(ASSETS_DIR) dist/$(PLUGIN_ID)/ +endif +ifneq ($(HAS_PUBLIC),) + cp -r public dist/$(PLUGIN_ID)/ +endif +ifneq ($(HAS_SERVER),) + mkdir -p dist/$(PLUGIN_ID)/server + cp -r server/dist dist/$(PLUGIN_ID)/server/ +endif +ifneq ($(HAS_WEBAPP),) + mkdir -p dist/$(PLUGIN_ID)/webapp + cp -r webapp/dist dist/$(PLUGIN_ID)/webapp/ +endif +ifeq ($(shell uname),Darwin) + cd dist && tar --disable-copyfile -cvzf $(BUNDLE_NAME) $(PLUGIN_ID) +else + cd dist && tar -cvzf $(BUNDLE_NAME) $(PLUGIN_ID) +endif + + @echo plugin built at: dist/$(BUNDLE_NAME) + +## Builds and bundles the plugin. +.PHONY: dist +dist: apply server webapp bundle diff --git a/build/deploy.mk b/build/deploy.mk new file mode 100644 index 00000000..a9c0fe01 --- /dev/null +++ b/build/deploy.mk @@ -0,0 +1,52 @@ +# ==================================================================================== +# Deployment and Plugin Management +# ==================================================================================== + +## Applies the plugin manifest to the server and webapp codebase +.PHONY: apply +apply: + pluginctl manifest apply + +## Builds and installs the plugin to a server. +.PHONY: deploy +deploy: dist + pluginctl deploy --bundle-path dist/$(BUNDLE_NAME) + +## Builds and installs the plugin to a server, updating the webapp automatically when changed. +.PHONY: watch +watch: apply server bundle +ifeq ($(MM_DEBUG),) + cd webapp && $(NPM) run build:watch +else + cd webapp && $(NPM) run debug:watch +endif + +## Installs a previous built plugin with updated webpack assets to a server. +.PHONY: deploy-from-watch +deploy-from-watch: bundle + pluginctl deploy --bundle-path dist/$(BUNDLE_NAME) + +## Disable the plugin. +.PHONY: disable +disable: detach + pluginctl disable + +## Enable the plugin. +.PHONY: enable +enable: + pluginctl enable + +## Reset the plugin, effectively disabling and re-enabling it on the server. +.PHONY: reset +reset: detach + pluginctl reset + +## View plugin logs. +.PHONY: logs +logs: + pluginctl logs + +## Watch plugin logs. +.PHONY: logs-watch +logs-watch: + pluginctl logs --watch diff --git a/build/dev.mk b/build/dev.mk new file mode 100644 index 00000000..a8ca0ce3 --- /dev/null +++ b/build/dev.mk @@ -0,0 +1,53 @@ +# ==================================================================================== +# Development and Debugging +# ==================================================================================== + +## Setup dlv for attaching, identifying the plugin PID for other targets. +.PHONY: setup-attach +setup-attach: + $(eval PLUGIN_PID := $(shell ps aux | grep "plugins/${PLUGIN_ID}" | grep -v "grep" | awk -F " " '{print $$2}')) + $(eval NUM_PID := $(shell echo -n ${PLUGIN_PID} | wc -w)) + + @if [ ${NUM_PID} -gt 2 ]; then \ + echo "** There is more than 1 plugin process running. Run 'make kill reset' to restart just one."; \ + exit 1; \ + fi + +## Check if setup-attach succeeded. +.PHONY: check-attach +check-attach: + @if [ -z ${PLUGIN_PID} ]; then \ + echo "Could not find plugin PID; the plugin is not running. Exiting."; \ + exit 1; \ + else \ + echo "Located Plugin running with PID: ${PLUGIN_PID}"; \ + fi + +## Attach dlv to an existing plugin instance. +.PHONY: attach +attach: setup-attach check-attach + dlv attach ${PLUGIN_PID} + +## Attach dlv to an existing plugin instance, exposing a headless instance on $DLV_DEBUG_PORT. +.PHONY: attach-headless +attach-headless: setup-attach check-attach + dlv attach ${PLUGIN_PID} --listen :$(DLV_DEBUG_PORT) --headless=true --api-version=2 --accept-multiclient + +## Detach dlv from an existing plugin instance, if previously attached. +.PHONY: detach +detach: setup-attach + @DELVE_PID=$(shell ps aux | grep "dlv attach ${PLUGIN_PID}" | grep -v "grep" | awk -F " " '{print $$2}') && \ + if [ "$$DELVE_PID" -gt 0 ] > /dev/null 2>&1 ; then \ + echo "Located existing delve process running with PID: $$DELVE_PID. Killing." ; \ + kill -9 $$DELVE_PID ; \ + fi + +## Kill all instances of the plugin, detaching any existing dlv instance. +.PHONY: kill +kill: detach + $(eval PLUGIN_PID := $(shell ps aux | grep "plugins/${PLUGIN_ID}" | grep -v "grep" | awk -F " " '{print $$2}')) + + @for PID in ${PLUGIN_PID}; do \ + echo "Killing plugin pid $$PID"; \ + kill -9 $$PID; \ + done; \ \ No newline at end of file diff --git a/build/manifest/main.go b/build/manifest/main.go deleted file mode 100644 index 957a5002..00000000 --- a/build/manifest/main.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/mattermost/mattermost/server/public/model" - "github.com/pkg/errors" -) - -func main() { - if len(os.Args) <= 1 { - panic("no cmd specified") - } - - manifest, err := findManifest() - if err != nil { - panic("failed to find manifest: " + err.Error()) - } - - cmd := os.Args[1] - switch cmd { - case "id": - dumpPluginID(manifest) - - case "version": - dumpPluginVersion(manifest) - - case "has_server": - if manifest.HasServer() { - fmt.Printf("true") - } - - case "has_webapp": - if manifest.HasWebapp() { - fmt.Printf("true") - } - - default: - panic("unrecognized command: " + cmd) - } -} - -func findManifest() (*model.Manifest, error) { - _, manifestFilePath, err := model.FindManifest(".") - if err != nil { - return nil, errors.Wrap(err, "failed to find manifest in current working directory") - } - manifestFile, err := os.Open(manifestFilePath) - if err != nil { - return nil, errors.Wrapf(err, "failed to open %s", manifestFilePath) - } - defer manifestFile.Close() - - // Re-decode the manifest, disallowing unknown fields. When we write the manifest back out, - // we don't want to accidentally clobber anything we won't preserve. - var manifest model.Manifest - decoder := json.NewDecoder(manifestFile) - decoder.DisallowUnknownFields() - if err = decoder.Decode(&manifest); err != nil { - return nil, errors.Wrap(err, "failed to parse manifest") - } - - return &manifest, nil -} - -// dumpPluginId writes the plugin id from the given manifest to standard out -func dumpPluginID(manifest *model.Manifest) { - fmt.Printf("%s", manifest.Id) -} - -// dumpPluginVersion writes the plugin version from the given manifest to standard out -func dumpPluginVersion(manifest *model.Manifest) { - fmt.Printf("%s", manifest.Version) -} diff --git a/build/pluginctl/main.go b/build/pluginctl/main.go deleted file mode 100644 index 54572fc0..00000000 --- a/build/pluginctl/main.go +++ /dev/null @@ -1,180 +0,0 @@ -// main handles deployment of the plugin to a development server using the Client4 API. -package main - -import ( - "context" - "errors" - "fmt" - "log" - "net" - "os" - "time" - - "github.com/mattermost/mattermost/server/public/model" -) - -const commandTimeout = 120 * time.Second - -const helpText = ` -Usage: - pluginctl deploy - pluginctl disable - pluginctl enable - pluginctl reset -` - -func main() { - err := pluginctl() - if err != nil { - fmt.Printf("Failed: %s\n", err.Error()) - fmt.Print(helpText) - os.Exit(1) - } -} - -func pluginctl() error { - if len(os.Args) < 3 { - return errors.New("invalid number of arguments") - } - - ctx, cancel := context.WithTimeout(context.Background(), commandTimeout) - defer cancel() - - client, err := getClient(ctx) - if err != nil { - return err - } - - switch os.Args[1] { - case "deploy": - if len(os.Args) < 4 { - return errors.New("invalid number of arguments") - } - return deploy(ctx, client, os.Args[2], os.Args[3]) - case "disable": - return disablePlugin(ctx, client, os.Args[2]) - case "enable": - return enablePlugin(ctx, client, os.Args[2]) - case "reset": - return resetPlugin(ctx, client, os.Args[2]) - default: - return errors.New("invalid second argument") - } -} - -func getClient(ctx context.Context) (*model.Client4, error) { - socketPath := os.Getenv("MM_LOCALSOCKETPATH") - if socketPath == "" { - socketPath = model.LocalModeSocketPath - } - - client, connected := getUnixClient(socketPath) - if connected { - log.Printf("Connecting using local mode over %s", socketPath) - return client, nil - } - - if os.Getenv("MM_LOCALSOCKETPATH") != "" { - log.Printf("No socket found at %s for local mode deployment. Attempting to authenticate with credentials.", socketPath) - } - - siteURL := os.Getenv("MM_SERVICESETTINGS_SITEURL") - adminToken := os.Getenv("MM_ADMIN_TOKEN") - adminUsername := os.Getenv("MM_ADMIN_USERNAME") - adminPassword := os.Getenv("MM_ADMIN_PASSWORD") - - if siteURL == "" { - return nil, errors.New("MM_SERVICESETTINGS_SITEURL is not set") - } - - client = model.NewAPIv4Client(siteURL) - - if adminToken != "" { - log.Printf("Authenticating using token against %s.", siteURL) - client.SetToken(adminToken) - return client, nil - } - - if adminUsername != "" && adminPassword != "" { - client := model.NewAPIv4Client(siteURL) - log.Printf("Authenticating as %s against %s.", adminUsername, siteURL) - _, _, err := client.Login(ctx, adminUsername, adminPassword) - if err != nil { - return nil, fmt.Errorf("failed to login as %s: %w", adminUsername, err) - } - - return client, nil - } - - return nil, errors.New("one of MM_ADMIN_TOKEN or MM_ADMIN_USERNAME/MM_ADMIN_PASSWORD must be defined") -} - -func getUnixClient(socketPath string) (*model.Client4, bool) { - _, err := net.Dial("unix", socketPath) - if err != nil { - return nil, false - } - - return model.NewAPIv4SocketClient(socketPath), true -} - -// deploy attempts to upload and enable a plugin via the Client4 API. -// It will fail if plugin uploads are disabled. -func deploy(ctx context.Context, client *model.Client4, pluginID, bundlePath string) error { - pluginBundle, err := os.Open(bundlePath) - if err != nil { - return fmt.Errorf("failed to open %s: %w", bundlePath, err) - } - defer pluginBundle.Close() - - log.Print("Uploading plugin via API.") - _, _, err = client.UploadPluginForced(ctx, pluginBundle) - if err != nil { - return fmt.Errorf("failed to upload plugin bundle: %s", err.Error()) - } - - log.Print("Enabling plugin.") - _, err = client.EnablePlugin(ctx, pluginID) - if err != nil { - return fmt.Errorf("failed to enable plugin: %s", err.Error()) - } - - return nil -} - -// disablePlugin attempts to disable the plugin via the Client4 API. -func disablePlugin(ctx context.Context, client *model.Client4, pluginID string) error { - log.Print("Disabling plugin.") - _, err := client.DisablePlugin(ctx, pluginID) - if err != nil { - return fmt.Errorf("failed to disable plugin: %w", err) - } - - return nil -} - -// enablePlugin attempts to enable the plugin via the Client4 API. -func enablePlugin(ctx context.Context, client *model.Client4, pluginID string) error { - log.Print("Enabling plugin.") - _, err := client.EnablePlugin(ctx, pluginID) - if err != nil { - return fmt.Errorf("failed to enable plugin: %w", err) - } - - return nil -} - -// resetPlugin attempts to reset the plugin via the Client4 API. -func resetPlugin(ctx context.Context, client *model.Client4, pluginID string) error { - err := disablePlugin(ctx, client, pluginID) - if err != nil { - return err - } - - err = enablePlugin(ctx, client, pluginID) - if err != nil { - return err - } - - return nil -} diff --git a/build/test.mk b/build/test.mk new file mode 100644 index 00000000..16261dad --- /dev/null +++ b/build/test.mk @@ -0,0 +1,59 @@ +# ==================================================================================== +# Testing and Quality Assurance +# ==================================================================================== + +GOLANGCI_LINT_BINARY = ./build/bin/golangci-lint +GOTESTSUM_BINARY = ./build/bin/gotestsum + +## Install go tools +install-go-tools: + @echo "Installing development tools..." + @pluginctl tools install --bin-dir ./build/bin + +## Runs eslint and golangci-lint +.PHONY: check-style +check-style: manifest-check apply webapp/node_modules install-go-tools + @echo Checking for style guide compliance + +ifneq ($(HAS_WEBAPP),) + cd webapp && npm run lint + cd webapp && npm run check-types +endif + +# It's highly recommended to run go-vet first +# to find potential compile errors that could introduce +# weird reports at golangci-lint step +ifneq ($(HAS_SERVER),) + @echo Running golangci-lint + $(GO) vet ./... + $(GOLANGCI_LINT_BINARY) run ./... +endif + +## Runs any lints and unit tests defined for the server and webapp, if they exist. +.PHONY: test +test: apply webapp/node_modules install-go-tools +ifneq ($(HAS_SERVER),) + $(GOTESTSUM_BINARY) -- -v ./... +endif +ifneq ($(HAS_WEBAPP),) + cd webapp && $(NPM) run test; +endif + +## Runs any lints and unit tests defined for the server and webapp, if they exist, optimized +## for a CI environment. +.PHONY: test-ci +test-ci: apply webapp/node_modules install-go-tools +ifneq ($(HAS_SERVER),) + $(GOTESTSUM_BINARY) --format standard-verbose --junitfile report.xml -- ./... +endif +ifneq ($(HAS_WEBAPP),) + cd webapp && $(NPM) run test; +endif + +## Creates a coverage report for the server code. +.PHONY: coverage +coverage: apply webapp/node_modules +ifneq ($(HAS_SERVER),) + $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... + $(GO) tool cover -html=server/coverage.txt +endif diff --git a/build/utils.mk b/build/utils.mk new file mode 100644 index 00000000..aa3fa3f7 --- /dev/null +++ b/build/utils.mk @@ -0,0 +1,42 @@ +# ==================================================================================== +# Utilities +# ==================================================================================== + +## Clean removes all build artifacts. +.PHONY: clean +clean: + rm -fr dist/ +ifneq ($(HAS_SERVER),) + rm -fr server/coverage.txt + rm -fr server/dist +endif +ifneq ($(HAS_WEBAPP),) + rm -fr webapp/junit.xml + rm -fr webapp/dist + rm -fr webapp/node_modules +endif + rm -fr build/bin/ + +## Extract strings for translation from the source code. +.PHONY: i18n-extract +i18n-extract: +ifneq ($(HAS_WEBAPP),) +ifeq ($(HAS_MM_UTILITIES),) + @echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command" +else + cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir $(PWD)/webapp +endif +endif + +## Generate mocks for testing. +.PHONY: mock +mock: +ifneq ($(HAS_SERVER),) + go install github.com/golang/mock/mockgen@v1.6.0 + mockgen -destination=server/command/mocks/mock_commands.go -package=mocks github.com/mattermost/mattermost-plugin-demo/server/command Command +endif + +## Show help documentation. +.PHONY: help +help: + @cat Makefile build/*.mk | grep -v '\.PHONY' | grep -v '\help:' | grep -B1 -E '^[a-zA-Z0-9_.-]+:.*' | sed -e "s/:.*//g" | sed -e "s/^## //g" | grep -v '\-\-' | sed '1!G;h;$$!d' | awk 'NR%2{printf "\033[36m%-30s\033[0m",$$0;next;}1' | sort diff --git a/build/versioning.mk b/build/versioning.mk new file mode 100644 index 00000000..06d660a2 --- /dev/null +++ b/build/versioning.mk @@ -0,0 +1,111 @@ +# ==================================================================================== +# Semantic Versioning +# ==================================================================================== + +# Used for semver bumping +PROTECTED_BRANCH := master +APP_NAME := $(shell pluginctl manifest get '{{.Id}}') +CURRENT_VERSION := $(shell git describe --abbrev=0 --tags) +VERSION_PARTS := $(subst ., ,$(subst v,,$(subst -rc, ,$(CURRENT_VERSION)))) +MAJOR := $(word 1,$(VERSION_PARTS)) +MINOR := $(word 2,$(VERSION_PARTS)) +PATCH := $(word 3,$(VERSION_PARTS)) +RC := $(shell echo $(CURRENT_VERSION) | grep -oE 'rc[0-9]+' | sed 's/rc//') + +# Check if current branch is protected +define check_protected_branch + @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + if ! echo "$(PROTECTED_BRANCH)" | grep -wq "$$current_branch" && ! echo "$$current_branch" | grep -q "^release"; then \ + echo "Error: Tagging is only allowed from $(PROTECTED_BRANCH) or release branches. You are on $$current_branch branch."; \ + exit 1; \ + fi +endef + +# Check if there are pending pulls +define check_pending_pulls + @git fetch; \ + current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/$$current_branch)" ]; then \ + echo "Error: Your branch is not up to date with upstream. Please pull the latest changes before performing a release"; \ + exit 1; \ + fi +endef + +# Prompt for approval +define prompt_approval + @read -p "About to bump $(APP_NAME) to version $(1), approve? (y/n) " userinput; \ + if [ "$$userinput" != "y" ]; then \ + echo "Bump aborted."; \ + exit 1; \ + fi +endef + +.PHONY: patch minor major patch-rc minor-rc major-rc + +patch: ## to bump patch version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval PATCH := $(shell echo $$(($(PATCH)+1)))) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) + @echo Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)" + git push origin v$(MAJOR).$(MINOR).$(PATCH) + @echo Bumped $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) + +minor: ## to bump minor version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) + @$(eval PATCH := 0) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) + @echo Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)" + git push origin v$(MAJOR).$(MINOR).$(PATCH) + @echo Bumped $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) + +major: ## to bump major version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + $(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) + $(eval MINOR := 0) + $(eval PATCH := 0) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) + @echo Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)" + git push origin v$(MAJOR).$(MINOR).$(PATCH) + @echo Bumped $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) + +patch-rc: ## to bump patch release candidate version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval RC := $(shell echo $$(($(RC)+1)))) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) + @echo Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" + git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + @echo Bumped $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + +minor-rc: ## to bump minor release candidate version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) + @$(eval PATCH := 0) + @$(eval RC := 1) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) + @echo Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" + git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + @echo Bumped $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + +major-rc: ## to bump major release candidate version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) + @$(eval MINOR := 0) + @$(eval PATCH := 0) + @$(eval RC := 1) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) + @echo Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" + git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + @echo Bumped $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) diff --git a/plugin.json b/plugin.json index dc0831e5..3053069d 100644 --- a/plugin.json +++ b/plugin.json @@ -1,148 +1,179 @@ { - "id": "com.mattermost.demo-plugin", - "name": "Demo Plugin", - "description": "This plugin demonstrates the capabilities of a Mattermost plugin.", - "homepage_url": "https://github.com/mattermost/mattermost-plugin-demo", - "support_url": "https://github.com/mattermost/mattermost-plugin-demo/issues", - "icon_path": "assets/icon.svg", - "version": "0.10.0", - "min_server_version": "7.9.0", - "server": { - "executables": { - "linux-amd64": "server/dist/plugin-linux-amd64", - "linux-arm64": "server/dist/plugin-linux-arm64", - "darwin-amd64": "server/dist/plugin-darwin-amd64", - "darwin-arm64": "server/dist/plugin-darwin-arm64", - "windows-amd64": "server/dist/plugin-windows-amd64.exe" - } + "id": "com.mattermost.demo-plugin", + "name": "Demo Plugin", + "description": "This plugin demonstrates the capabilities of a Mattermost plugin.", + "homepage_url": "https://github.com/mattermost/mattermost-plugin-demo", + "support_url": "https://github.com/mattermost/mattermost-plugin-demo/issues", + "icon_path": "assets/icon.svg", + "version": "0.10.0", + "min_server_version": "7.9.0", + "server": { + "executables": { + "darwin-amd64": "server/dist/plugin-darwin-amd64", + "darwin-arm64": "server/dist/plugin-darwin-arm64", + "linux-amd64": "server/dist/plugin-linux-amd64", + "linux-arm64": "server/dist/plugin-linux-arm64", + "windows-amd64": "server/dist/plugin-windows-amd64.exe" }, - "webapp": { - "bundle_path": "webapp/dist/main.js" + "executable": "" + }, + "webapp": { + "bundle_path": "webapp/dist/main.js" + }, + "settings_schema": { + "header": "Header: Configure your demo plugin settings below.", + "footer": "Footer: The code for this demo plugin can be found [here](https://github.com/mattermost/mattermost-plugin-demo).", + "settings": [ + { + "key": "ChannelName", + "display_name": "Channel Name:", + "type": "text", + "help_text": "The channel to use as part of the demo plugin, created for each team automatically if it does not exist.", + "placeholder": "demo_plugin", + "default": "demo_plugin", + "hosting": "", + "secret": false + }, + { + "key": "Username", + "display_name": "Username:", + "type": "text", + "help_text": "The user to use as part of the demo plugin, created automatically if it does not exist.", + "placeholder": "demo_plugin", + "default": "demo_plugin", + "hosting": "", + "secret": false + }, + { + "key": "LastName", + "display_name": "Demo User Last Name:", + "type": "radio", + "help_text": "Select the last name for the demo user.", + "placeholder": "", + "default": "Plugin User", + "options": [ + { + "display_name": "Plugin User", + "value": "Plugin User" + }, + { + "display_name": "Demoson III", + "value": "Demoson III" + }, + { + "display_name": "McDemo", + "value": "McDemo" + } + ], + "hosting": "", + "secret": false + }, + { + "key": "TextStyle", + "display_name": "Text Style:", + "type": "dropdown", + "help_text": "Change the text style of the messages posted by this plugin.", + "placeholder": "", + "default": "", + "options": [ + { + "display_name": "none", + "value": "" + }, + { + "display_name": "italics", + "value": "_" + }, + { + "display_name": "bold", + "value": "**" + } + ], + "hosting": "", + "secret": false + }, + { + "key": "RandomSecret", + "display_name": "Random Secret:", + "type": "generated", + "help_text": "Generate a random string that the demo plugin will watch for. If the secret string is mentioned in any channel then the demo plugin will publish a special message.", + "regenerate_help_text": "Generate a new secret string.", + "placeholder": "", + "default": "CFgcq9Hr9OKSevvqH_SH-mPlgVklmpUm", + "hosting": "", + "secret": false + }, + { + "key": "SecretMessage", + "display_name": "Secret Message:", + "type": "custom", + "help_text": "The message posted by the demo plugin when the secret phrase is detected.", + "placeholder": "", + "default": "Yay! The random secret string was posted! Go to the settings page for this plugin in the System Console to generate a new random secret.", + "hosting": "", + "secret": false + }, + { + "key": "CustomSetting", + "display_name": "", + "type": "custom", + "help_text": "", + "placeholder": "", + "default": null, + "hosting": "", + "secret": false + }, + { + "key": "EnableMentionUser", + "display_name": "Enable Mention User:", + "type": "bool", + "help_text": "Enable or disable the demo plugin to tag a username on every message sent. The username value is set below.", + "placeholder": "", + "default": false, + "hosting": "", + "secret": false + }, + { + "key": "MentionUser", + "display_name": "Mention User:", + "type": "username", + "help_text": "Configure the username to be mentioned by the demo plugin. Must be enabled in the setting above.", + "placeholder": "demo_plugin", + "default": "demo_plugin", + "hosting": "", + "secret": false + }, + { + "key": "secretNumber", + "display_name": "Secret Number:", + "type": "number", + "help_text": "A secret number that the demo plugin will watch for. If the secret number is mentioned in any channel then the demo plugin will publish a special message.", + "placeholder": "Some secret number", + "default": 123, + "hosting": "", + "secret": false + }, + { + "key": "integrationRequestDelay", + "display_name": "Integration Request delay", + "type": "number", + "help_text": "A deplay in seconds that is applied to Slash Command responses, Post Actions responses and Interactive Dialog responses. It's useful for testing.", + "placeholder": "A delay in seconds", + "default": 0, + "hosting": "", + "secret": false + } + ], + "sections": null + }, + "props": { + "pluginctl": { + "version": "v0.1.5-0.20250814142442-19c417d53e1d", + "ignore_assets": [ + "webapp/babel.config.js", + "webapp/webpack.config.js" + ] }, - "settings_schema": { - "header": "Header: Configure your demo plugin settings below.", - "footer": "Footer: The code for this demo plugin can be found [here](https://github.com/mattermost/mattermost-plugin-demo).", - "settings": [ - { - "key": "ChannelName", - "display_name": "Channel Name:", - "type": "text", - "help_text": "The channel to use as part of the demo plugin, created for each team automatically if it does not exist.", - "placeholder": "demo_plugin", - "default": "demo_plugin" - }, - { - "key": "Username", - "display_name": "Username:", - "type": "text", - "help_text": "The user to use as part of the demo plugin, created automatically if it does not exist.", - "placeholder": "demo_plugin", - "default": "demo_plugin" - }, - { - "key": "LastName", - "display_name": "Demo User Last Name:", - "type": "radio", - "help_text": "Select the last name for the demo user.", - "placeholder": "", - "default": "Plugin User", - "options": [ - { - "display_name": "Plugin User", - "value": "Plugin User" - }, - { - "display_name": "Demoson III", - "value": "Demoson III" - }, - { - "display_name": "McDemo", - "value": "McDemo" - } - ] - }, - { - "key": "TextStyle", - "display_name": "Text Style:", - "type": "dropdown", - "help_text": "Change the text style of the messages posted by this plugin.", - "placeholder": "", - "default": "", - "options": [ - { - "display_name": "none", - "value": "" - }, - { - "display_name": "italics", - "value": "_" - }, - { - "display_name": "bold", - "value": "**" - } - ] - }, - { - "key": "RandomSecret", - "display_name": "Random Secret:", - "type": "generated", - "help_text": "Generate a random string that the demo plugin will watch for. If the secret string is mentioned in any channel then the demo plugin will publish a special message.", - "regenerate_help_text": "Generate a new secret string.", - "placeholder": "", - "default": "CFgcq9Hr9OKSevvqH_SH-mPlgVklmpUm" - }, - { - "key": "SecretMessage", - "display_name": "Secret Message:", - "type": "custom", - "help_text": "The message posted by the demo plugin when the secret phrase is detected.", - "placeholder": "", - "default": "Yay! The random secret string was posted! Go to the settings page for this plugin in the System Console to generate a new random secret." - }, - { - "key": "CustomSetting", - "display_name": "", - "type": "custom", - "help_text": "", - "placeholder": "", - "default": null - }, - { - "key": "EnableMentionUser", - "display_name": "Enable Mention User:", - "type": "bool", - "help_text": "Enable or disable the demo plugin to tag a username on every message sent. The username value is set below.", - "placeholder": "", - "default": false - }, - { - "key": "MentionUser", - "display_name": "Mention User:", - "type": "username", - "help_text": "Configure the username to be mentioned by the demo plugin. Must be enabled in the setting above.", - "placeholder": "demo_plugin", - "default": "demo_plugin" - }, - { - "key": "secretNumber", - "display_name": "Secret Number:", - "type": "number", - "help_text": "A secret number that the demo plugin will watch for. If the secret number is mentioned in any channel then the demo plugin will publish a special message.", - "placeholder": "Some secret number", - "default": 123 - }, - { - "key": "integrationRequestDelay", - "display_name": "Integration Request delay", - "type": "number", - "help_text": "A deplay in seconds that is applied to Slash Command responses, Post Actions responses and Interactive Dialog responses. It's useful for testing.", - "placeholder": "A delay in seconds", - "default": 0 - } - ] - }, - "props": { - "support_packet": "Demo plugin support packet" - } -} + "support_packet": "Demo plugin support packet" + } +} \ No newline at end of file From 7e22e69d54799b9aed5f4410b4aaadac2ed19c55 Mon Sep 17 00:00:00 2001 From: Felipe Martin Date: Thu, 14 Aug 2025 16:53:20 +0200 Subject: [PATCH 2/2] refactor: starter-template way of handling the manifest --- .gitignore | 3 +++ build/legacy.mk | 3 --- plugin.go | 18 ------------------ server/plugin.go | 7 ------- webapp/src/manifest.js | 5 ----- 5 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 build/legacy.mk delete mode 100644 plugin.go delete mode 100644 webapp/src/manifest.js diff --git a/.gitignore b/.gitignore index 4f1b54b2..728bafd5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ dist # VSCode .vscode + +webapp/src/manifest.ts +server/manifest.go diff --git a/build/legacy.mk b/build/legacy.mk deleted file mode 100644 index 0c327339..00000000 --- a/build/legacy.mk +++ /dev/null @@ -1,3 +0,0 @@ -.PHONY: apply -apply: - @echo make apply is deprecated and has no effect. diff --git a/plugin.go b/plugin.go deleted file mode 100644 index c12d7bd8..00000000 --- a/plugin.go +++ /dev/null @@ -1,18 +0,0 @@ -package root - -import ( - _ "embed" // Need to embed manifest file - "encoding/json" - "strings" - - "github.com/mattermost/mattermost/server/public/model" -) - -//go:embed plugin.json -var manifestString string - -var Manifest model.Manifest - -func init() { - _ = json.NewDecoder(strings.NewReader(manifestString)).Decode(&Manifest) -} diff --git a/server/plugin.go b/server/plugin.go index cf55339d..b7de0c9c 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -5,16 +5,9 @@ import ( "github.com/gorilla/mux" - "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/pluginapi" "github.com/mattermost/mattermost/server/public/pluginapi/cluster" - - root "github.com/mattermost/mattermost-plugin-demo" -) - -var ( - manifest model.Manifest = root.Manifest ) type Plugin struct { diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js deleted file mode 100644 index 5e14f599..00000000 --- a/webapp/src/manifest.js +++ /dev/null @@ -1,5 +0,0 @@ -import manifest from '../../plugin.json'; - -export default manifest; -export const id = manifest.id; -export const version = manifest.version;