From a49d6f6dd46f9941be0e2f486bdb4b5d04be9d3a Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Mon, 22 Jun 2020 12:21:37 -0300 Subject: [PATCH] Various tooling improvements from other plugins (#96) --- CHANGELOG.md | 9 - Makefile | 138 +++++++++++---- README.md | 23 ++- build/deploy/main.go | 122 -------------- build/manifest/main.go | 4 +- build/pluginctl/main.go | 147 ++++++++++++++++ build/setup.mk | 2 +- go.mod | 1 - go.sum | 23 --- webapp/.gitignore | 4 +- webapp/.npmrc | 1 + webapp/package-lock.json | 356 +++++++++++++++++++++++++++++++++++++++ webapp/package.json | 9 +- webapp/webpack.config.js | 55 +++++- 14 files changed, 693 insertions(+), 201 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 build/deploy/main.go create mode 100644 build/pluginctl/main.go create mode 100644 webapp/.npmrc diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index b682ae5..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## 0.0.1 - 2018-08-16 -### Added -- Initial release diff --git a/Makefile b/Makefile index 771cdf5..6e7b837 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,23 @@ GO ?= $(shell command -v go 2> /dev/null) NPM ?= $(shell command -v npm 2> /dev/null) CURL ?= $(shell command -v curl 2> /dev/null) +DEBUG ?= MANIFEST_FILE ?= plugin.json GOPATH ?= $(shell go env GOPATH) GO_TEST_FLAGS ?= -race GO_BUILD_FLAGS ?= MM_UTILITIES_DIR ?= ../mattermost-utilities +DLV_DEBUG_PORT := 2346 export GO111MODULE=on # You can include assets this directory into the bundle. This can be e.g. used to include profile pictures. ASSETS_DIR ?= assets +## Define the default target (make all) +.PHONY: default +default: all + # Verify environment, and define PLUGIN_ID, PLUGIN_VERSION, HAS_SERVER and HAS_WEBAPP as needed. include build/setup.mk @@ -23,25 +29,24 @@ ifneq ($(wildcard build/custom.mk),) endif ## Checks the code style, tests, builds and bundles the plugin. +.PHONY: all all: check-style test dist -## Propagates plugin manifest information into the server/ and webapp/ folders as required. +## Propagates plugin manifest information into the server/ and webapp/ folders. .PHONY: apply apply: ./build/bin/manifest apply -## Runs golangci-lint and eslint. +## Runs eslint and golangci-lint .PHONY: check-style -check-style: webapp/.npminstall golangci-lint +check-style: webapp/node_modules @echo Checking for style guide compliance ifneq ($(HAS_WEBAPP),) cd webapp && npm run lint endif -## Run golangci-lint on codebase. -.PHONY: golangci-lint -golangci-lint: +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; \ @@ -49,19 +54,28 @@ golangci-lint: @echo Running golangci-lint golangci-lint run ./... +endif -## Builds the server, if it exists, including support for multiple architectures. +## Builds the server, if it exists, for all supported architectures. .PHONY: server server: ifneq ($(HAS_SERVER),) mkdir -p server/dist; +ifeq ($(DEBUG),) cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-linux-amd64; cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-darwin-amd64; cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-windows-amd64.exe; +else + $(info DEBUG mode is on; to disable, unset DEBUG) + + cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -o dist/plugin-darwin-amd64; + cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -o dist/plugin-linux-amd64; + cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -o dist/plugin-windows-amd64.exe; +endif endif ## Ensures NPM dependencies are installed without having to run this all the time. -webapp/.npminstall: webapp/package.json +webapp/node_modules: webapp/package.json ifneq ($(HAS_WEBAPP),) cd webapp && $(NPM) install touch $@ @@ -69,17 +83,13 @@ endif ## Builds the webapp, if it exists. .PHONY: webapp -webapp: webapp/.npminstall +webapp: webapp/node_modules ifneq ($(HAS_WEBAPP),) +ifeq ($(DEBUG),) cd webapp && $(NPM) run build; +else + cd webapp && $(NPM) run debug; endif - -## Builds the webapp in debug mode, if it exists. -.PHONY: webapp-debug -webapp-debug: webapp/.npminstall -ifneq ($(HAS_WEBAPP),) - cd webapp && \ - $(NPM) run debug; endif ## Generates a tar bundle of the plugin for install. @@ -110,32 +120,78 @@ endif .PHONY: dist dist: apply server webapp bundle -## Installs the plugin to a (development) server. -## It uses the API if appropriate environment variables are defined, -## and otherwise falls back to trying to copy the plugin to a sibling mattermost-server directory. +## Builds and installs the plugin to a server. .PHONY: deploy deploy: dist - ./build/bin/deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME) + ./build/bin/pluginctl deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME) -.PHONY: debug-deploy -debug-deploy: debug-dist deploy +## Builds and installs the plugin to a server, updating the webapp automatically when changed. +.PHONY: watch +watch: apply server bundle +ifeq ($(DEBUG),) + cd webapp && $(NPM) run build:watch +else + cd webapp && $(NPM) run debug:watch +endif -.PHONY: debug-dist -debug-dist: apply server webapp-debug bundle +## 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/.npminstall +test: webapp/node_modules ifneq ($(HAS_SERVER),) $(GO) test -v $(GO_TEST_FLAGS) ./server/... endif ifneq ($(HAS_WEBAPP),) - cd webapp && $(NPM) run fix && $(NPM) run test; + cd webapp && $(NPM) run test; endif ## Creates a coverage report for the server code. .PHONY: coverage -coverage: webapp/.npminstall +coverage: webapp/node_modules ifneq ($(HAS_SERVER),) $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... $(GO) tool cover -html=server/coverage.txt @@ -152,6 +208,31 @@ else 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: @@ -161,7 +242,6 @@ ifneq ($(HAS_SERVER),) rm -fr server/dist endif ifneq ($(HAS_WEBAPP),) - rm -fr webapp/.npminstall rm -fr webapp/junit.xml rm -fr webapp/dist rm -fr webapp/node_modules @@ -170,4 +250,4 @@ endif # Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html help: - @cat Makefile | 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 + @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 diff --git a/README.md b/README.md index 0a9d4df..616fbf0 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ This plugin serves as a starting point for writing a Mattermost plugin. Feel fre To learn more about plugins, see [our plugin documentation](https://developers.mattermost.com/extend/plugins/). ## Getting Started -Use GitHub's template feature to make a copy of this repository by clicking the "Use this template" button then clone outside of `$GOPATH`. +Use GitHub's template feature to make a copy of this repository by clicking the "Use this template" button. -Alternatively shallow clone the repository to a directory outside of `$GOPATH` matching your plugin name: +Alternatively shallow clone the repository matching your plugin name: ``` git clone --depth 1 https://github.com/mattermost/mattermost-plugin-starter-template com.example.my-plugin ``` -Note that this project uses [Go modules](https://github.com/golang/go/wiki/Modules). Be sure to locate the project outside of `$GOPATH`, or allow the use of Go modules within your `$GOPATH` with an `export GO111MODULE=on`. +Note that this project uses [Go modules](https://github.com/golang/go/wiki/Modules). Be sure to locate the project outside of `$GOPATH`. Edit `plugin.json` with your `id`, `name`, and `description`: ``` @@ -34,7 +34,9 @@ This will produce a single plugin file (with support for multiple architectures) dist/com.example.my-plugin.tar.gz ``` -There is a build target to automate deploying and enabling the plugin to your server, but it requires login credentials: +## Development + +To avoid having to manually install your plugin, build and deploy your plugin with login credentials: ``` export MM_SERVICESETTINGS_SITEURL=http://localhost:8065 export MM_ADMIN_USERNAME=admin @@ -42,16 +44,19 @@ export MM_ADMIN_PASSWORD=password make deploy ``` -or configuration of a [personal access token](https://docs.mattermost.com/developer/personal-access-tokens.html): +or with a [personal access token](https://docs.mattermost.com/developer/personal-access-tokens.html): ``` export MM_SERVICESETTINGS_SITEURL=http://localhost:8065 export MM_ADMIN_TOKEN=j44acwd8obn78cdcx7koid4jkr make deploy ``` -Alternatively, if you are running your `mattermost-server` out of a sibling directory by the same name, use the `deploy` target alone to unpack the files into the right directory. You will need to restart your server and manually enable your plugin. - -In production, deploy and upload your plugin via the [System Console](https://about.mattermost.com/default-plugin-uploads). +If developing a plugin with a webapp, watch for changes and deploy those automatically: +``` +export MM_SERVICESETTINGS_SITEURL=http://localhost:8065 +export MM_ADMIN_TOKEN=j44acwd8obn78cdcx7koid4jkr +make watch +``` ## Q&A @@ -80,4 +85,4 @@ if appErr := p.API.SetProfileImage(userID, profileImage); appErr != nil { ``` ### How do I build the plugin with unminified JavaScript? -Use `make debug-dist` and `make debug-deploy` in place of `make dist` and `make deploy` to configure webpack to generate unminified Javascript. +Use `make dist-debug` and `make deploy-debug` in place of `make dist` and `make deploy` to configure webpack to generate unminified Javascript. diff --git a/build/deploy/main.go b/build/deploy/main.go deleted file mode 100644 index 186bb49..0000000 --- a/build/deploy/main.go +++ /dev/null @@ -1,122 +0,0 @@ -// main handles deployment of the plugin to a development server using either the Client4 API -// or by copying the plugin bundle into a sibling mattermost-server/plugin directory. -package main - -import ( - "fmt" - "log" - "os" - "path/filepath" - - "github.com/mattermost/mattermost-server/v5/model" - "github.com/mholt/archiver/v3" - "github.com/pkg/errors" -) - -func main() { - err := deploy() - if err != nil { - fmt.Printf("Failed to deploy: %s\n", err.Error()) - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" deploy ") - os.Exit(1) - } -} - -// deploy handles deployment of the plugin to a development server. -func deploy() error { - if len(os.Args) < 3 { - return errors.New("invalid number of arguments") - } - - pluginID := os.Args[1] - bundlePath := os.Args[2] - - siteURL := os.Getenv("MM_SERVICESETTINGS_SITEURL") - adminToken := os.Getenv("MM_ADMIN_TOKEN") - adminUsername := os.Getenv("MM_ADMIN_USERNAME") - adminPassword := os.Getenv("MM_ADMIN_PASSWORD") - copyTargetDirectory, _ := filepath.Abs("../mattermost-server") - - if siteURL != "" { - client := model.NewAPIv4Client(siteURL) - - if adminToken != "" { - log.Printf("Authenticating using token against %s.", siteURL) - client.SetToken(adminToken) - - return uploadPlugin(client, pluginID, bundlePath) - } - - if adminUsername != "" && adminPassword != "" { - client := model.NewAPIv4Client(siteURL) - log.Printf("Authenticating as %s against %s.", adminUsername, siteURL) - _, resp := client.Login(adminUsername, adminPassword) - if resp.Error != nil { - return errors.Wrapf(resp.Error, "failed to login as %s", adminUsername) - } - - return uploadPlugin(client, pluginID, bundlePath) - } - } - - _, err := os.Stat(copyTargetDirectory) - if os.IsNotExist(err) { - return errors.New("no supported deployment method available, please install plugin manually") - } else if err != nil { - return errors.Wrapf(err, "failed to stat %s", copyTargetDirectory) - } - - log.Printf("Installing plugin to mattermost-server found in %s.", copyTargetDirectory) - log.Print("Server restart required to load updated plugin.") - return copyPlugin(pluginID, copyTargetDirectory, bundlePath) -} - -// uploadPlugin attempts to upload and enable a plugin via the Client4 API. -// It will fail if plugin uploads are disabled. -func uploadPlugin(client *model.Client4, pluginID, bundlePath string) error { - pluginBundle, err := os.Open(bundlePath) - if err != nil { - return errors.Wrapf(err, "failed to open %s", bundlePath) - } - defer pluginBundle.Close() - - log.Print("Uploading plugin via API.") - _, resp := client.UploadPluginForced(pluginBundle) - if resp.Error != nil { - return errors.Wrap(resp.Error, "failed to upload plugin bundle") - } - - log.Print("Enabling plugin.") - _, resp = client.EnablePlugin(pluginID) - if resp.Error != nil { - return errors.Wrap(resp.Error, "Failed to enable plugin") - } - - return nil -} - -// copyPlugin attempts to install a plugin by copying it to a sibling ../mattermost-server/plugin -// directory. A server restart is required before the plugin will start. -func copyPlugin(pluginID, targetPath, bundlePath string) error { - targetPath = filepath.Join(targetPath, "plugins") - - err := os.MkdirAll(targetPath, 0777) - if err != nil { - return errors.Wrapf(err, "failed to create %s", targetPath) - } - - existingPluginPath := filepath.Join(targetPath, pluginID) - err = os.RemoveAll(existingPluginPath) - if err != nil { - return errors.Wrapf(err, "failed to remove existing existing plugin directory %s", existingPluginPath) - } - - err = archiver.Unarchive(bundlePath, targetPath) - if err != nil { - return errors.Wrapf(err, "failed to unarchive %s into %s", bundlePath, targetPath) - } - - return nil -} diff --git a/build/manifest/main.go b/build/manifest/main.go index c5eb60c..9d878c9 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -127,7 +127,7 @@ func applyManifest(manifest *model.Manifest) error { if err := ioutil.WriteFile( "server/manifest.go", []byte(fmt.Sprintf(pluginIDGoFileTemplate, manifestStr)), - 0644, + 0600, ); err != nil { return errors.Wrap(err, "failed to write server/manifest.go") } @@ -147,7 +147,7 @@ func applyManifest(manifest *model.Manifest) error { if err := ioutil.WriteFile( "webapp/src/manifest.js", []byte(fmt.Sprintf(pluginIDJSFileTemplate, manifestStr)), - 0644, + 0600, ); err != nil { return errors.Wrap(err, "failed to open webapp/src/manifest.js") } diff --git a/build/pluginctl/main.go b/build/pluginctl/main.go new file mode 100644 index 0000000..640e7fc --- /dev/null +++ b/build/pluginctl/main.go @@ -0,0 +1,147 @@ +// main handles deployment of the plugin to a development server using the Client4 API. +package main + +import ( + "errors" + "fmt" + "log" + "os" + + "github.com/mattermost/mattermost-server/v5/model" +) + +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") + } + + client, err := getClient() + 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(client, os.Args[2], os.Args[3]) + case "disable": + return disablePlugin(client, os.Args[2]) + case "enable": + return enablePlugin(client, os.Args[2]) + case "reset": + return resetPlugin(client, os.Args[2]) + default: + return errors.New("invalid second argument") + } +} + +func getClient() (*model.Client4, error) { + 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) + _, resp := client.Login(adminUsername, adminPassword) + if resp.Error != nil { + return nil, fmt.Errorf("failed to login as %s: %w", adminUsername, resp.Error) + } + return client, nil + } + + return nil, errors.New("one of MM_ADMIN_TOKEN or MM_ADMIN_USERNAME/MM_ADMIN_PASSWORD must be defined") +} + +// deploy attempts to upload and enable a plugin via the Client4 API. +// It will fail if plugin uploads are disabled. +func deploy(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.") + _, resp := client.UploadPluginForced(pluginBundle) + if resp.Error != nil { + return fmt.Errorf("failed to upload plugin bundle: %s", resp.Error.Error()) + } + + log.Print("Enabling plugin.") + _, resp = client.EnablePlugin(pluginID) + if resp.Error != nil { + return fmt.Errorf("failed to enable plugin: %s", resp.Error.Error()) + } + + return nil +} + +// disablePlugin attempts to disable the plugin via the Client4 API. +func disablePlugin(client *model.Client4, pluginID string) error { + log.Print("Disabling plugin.") + _, resp := client.DisablePlugin(pluginID) + if resp.Error != nil { + return fmt.Errorf("failed to disable plugin: %w", resp.Error) + } + + return nil +} + +// enablePlugin attempts to enable the plugin via the Client4 API. +func enablePlugin(client *model.Client4, pluginID string) error { + log.Print("Enabling plugin.") + _, resp := client.EnablePlugin(pluginID) + if resp.Error != nil { + return fmt.Errorf("failed to enable plugin: %w", resp.Error) + } + + return nil +} + +// resetPlugin attempts to reset the plugin via the Client4 API. +func resetPlugin(client *model.Client4, pluginID string) error { + err := disablePlugin(client, pluginID) + if err != nil { + return err + } + + err = enablePlugin(client, pluginID) + if err != nil { + return err + } + + return nil +} diff --git a/build/setup.mk b/build/setup.mk index bc1fdc3..493b06f 100644 --- a/build/setup.mk +++ b/build/setup.mk @@ -8,7 +8,7 @@ endif $(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/deploy && $(GO) build -o ../bin/deploy) +$(shell cd build/pluginctl && $(GO) build -o ../bin/pluginctl) # Extract the plugin id from the manifest. PLUGIN_ID ?= $(shell build/bin/manifest id) diff --git a/go.mod b/go.mod index ee0b848..de50d64 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.12 require ( github.com/mattermost/mattermost-server/v5 v5.20.0 - github.com/mholt/archiver/v3 v3.3.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.5.1 ) diff --git a/go.sum b/go.sum index c707656..432e148 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c= -github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -62,9 +60,6 @@ github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjC github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= -github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= @@ -94,8 +89,6 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 h1:KRMr9A3qfbVM7iV/WcLY/rL5LICqwMHLhwRXKu99fXw= -github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -107,8 +100,6 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -183,12 +174,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY= -github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM= -github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -227,8 +212,6 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= -github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.19/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/minio/minio-go/v6 v6.0.40/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= @@ -249,8 +232,6 @@ github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78Rwc github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= -github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= -github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -349,10 +330,6 @@ github.com/throttled/throttled v2.2.4+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLs github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= -github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= diff --git a/webapp/.gitignore b/webapp/.gitignore index e193ef9..55c64cd 100644 --- a/webapp/.gitignore +++ b/webapp/.gitignore @@ -1,3 +1,3 @@ -node_modules -.npminstall +.eslintcache junit.xml +node_modules diff --git a/webapp/.npmrc b/webapp/.npmrc new file mode 100644 index 0000000..cffe8cd --- /dev/null +++ b/webapp/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 1281e19..7c4fd6a 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -5753,6 +5753,17 @@ } } }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6095,6 +6106,82 @@ "randomfill": "^1.0.3" } }, + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.0.tgz", + "integrity": "sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -6131,6 +6218,12 @@ "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", "dev": true }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, "cssfontparser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", @@ -8902,6 +8995,15 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, "identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -8961,6 +9063,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -12388,6 +12496,92 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", + "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.16", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, "prefix-style": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/prefix-style/-/prefix-style-2.0.1.tgz", @@ -13664,6 +13858,68 @@ } } }, + "sass-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", + "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.6.1", + "semver": "^6.3.0" + }, + "dependencies": { + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.0.tgz", + "integrity": "sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -13759,6 +14015,15 @@ "safe-buffer": "^5.0.1" } }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shallow-equals": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shallow-equals/-/shallow-equals-1.0.0.tgz", @@ -14262,6 +14527,91 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "style-loader": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", + "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.6" + }, + "dependencies": { + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.0.tgz", + "integrity": "sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "style-value-types": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-3.1.7.tgz", @@ -14869,6 +15219,12 @@ "set-value": "^2.0.1" } }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", diff --git a/webapp/package.json b/webapp/package.json index 5d80b15..a850cc3 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -5,9 +5,11 @@ "main": "src/index.js", "scripts": { "build": "webpack --mode=production", + "build:watch": "webpack --mode=production --watch", "debug": "webpack --mode=none", - "lint": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet", - "fix": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --fix", + "debug:watch": "webpack --mode=development --watch", + "lint": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --cache", + "fix": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --fix --cache", "test": "jest --forceExit --detectOpenHandles --verbose", "test:watch": "jest --watch", "test-ci": "jest --forceExit --detectOpenHandles --maxWorkers=2" @@ -39,6 +41,7 @@ "babel-jest": "24.9.0", "babel-loader": "8.0.6", "babel-plugin-typescript-to-proptypes": "0.17.1", + "css-loader": "3.6.0", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.2", "enzyme-to-json": "3.4.3", @@ -52,6 +55,8 @@ "jest-canvas-mock": "2.2.0", "jest-junit": "10.0.0", "mattermost-webapp": "github:mattermost/mattermost-webapp#23f5f93d9f12a7e2b5623e5cee6814366abd9a0f", + "sass-loader": "8.0.2", + "style-loader": "1.2.1", "webpack": "4.35.0", "webpack-cli": "3.3.5" }, diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js index 592c3b1..ab7410a 100644 --- a/webapp/webpack.config.js +++ b/webapp/webpack.config.js @@ -1,4 +1,38 @@ -var path = require('path'); +const exec = require('child_process').exec; + +const path = require('path'); + +const PLUGIN_ID = require('../plugin.json').id; + +const NPM_TARGET = process.env.npm_lifecycle_event; //eslint-disable-line no-process-env +let mode = 'production'; +let devtool = ''; +if (NPM_TARGET === 'debug' || NPM_TARGET === 'debug:watch') { + mode = 'development'; + devtool = 'source-map'; +} + +const plugins = []; +if (NPM_TARGET === 'build:watch' || NPM_TARGET === 'debug:watch') { + plugins.push({ + apply: (compiler) => { + compiler.hooks.watchRun.tap('WatchStartPlugin', () => { + // eslint-disable-next-line no-console + console.log('Change detected. Rebuilding webapp.'); + }); + compiler.hooks.afterEmit.tap('AfterEmitPlugin', () => { + exec('cd .. && make deploy-from-watch', (err, stdout, stderr) => { + if (stdout) { + process.stdout.write(stdout); + } + if (stderr) { + process.stderr.write(stderr); + } + }); + }); + }, + }); +} module.exports = { entry: [ @@ -25,6 +59,21 @@ module.exports = { }, }, }, + { + test: /\.scss$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + }, + { + loader: 'sass-loader', + options: { + includePaths: ['node_modules/compass-mixins/lib', 'sass'], + }, + }, + ], + }, ], }, externals: { @@ -36,8 +85,12 @@ module.exports = { 'react-router-dom': 'ReactRouterDom', }, output: { + devtoolNamespace: PLUGIN_ID, path: path.join(__dirname, '/dist'), publicPath: '/', filename: 'main.js', }, + devtool, + mode, + plugins, };