🎉🚀 May the concensus be with us!
This commit is contained in:
commit
48328e7db2
33 changed files with 4051 additions and 0 deletions
169
.gitignore
vendored
Normal file
169
.gitignore
vendored
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/go,node
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=go,node
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/go,node
|
||||||
|
|
||||||
|
*.db
|
16
cmd/main.go
Normal file
16
cmd/main.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var App = cli.App{
|
||||||
|
Name: "govote",
|
||||||
|
Usage: "🌈 Referendums and concensus.",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
newCmd,
|
||||||
|
showCmd,
|
||||||
|
voteCmd,
|
||||||
|
serveCmd,
|
||||||
|
},
|
||||||
|
}
|
108
cmd/new.go
Normal file
108
cmd/new.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.c-base.org/baccenfutter/govote/store"
|
||||||
|
"code.c-base.org/baccenfutter/govote/utils"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/quorum"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/threshold"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var newCmd = &cli.Command{
|
||||||
|
Name: "new",
|
||||||
|
Usage: "➕ Create a voting",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "deadline",
|
||||||
|
Usage: "Duration for which this voting is open",
|
||||||
|
Aliases: []string{"D"},
|
||||||
|
Value: "1m",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "quorum",
|
||||||
|
Usage: "Minimum required number of participants",
|
||||||
|
Aliases: []string{"Q"},
|
||||||
|
Value: "SIMPLE",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "threshold",
|
||||||
|
Usage: "Minimum number of positive votes",
|
||||||
|
Aliases: []string{"T"},
|
||||||
|
Value: "SIMPLE",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "electors",
|
||||||
|
Usage: "Comma-separated list of eligible electors or empty if anyone can vote",
|
||||||
|
Aliases: []string{"E"},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "anonymous",
|
||||||
|
Usage: "Public visibility of votes.",
|
||||||
|
Aliases: []string{"A"},
|
||||||
|
Value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
deadline := ctx.String("deadline")
|
||||||
|
deadlineNum := fmt.Sprintf("%s", deadline[:len(deadline)-1])
|
||||||
|
deadlineUnit := fmt.Sprintf("%s", deadline[len(deadline)-1:])
|
||||||
|
|
||||||
|
deadlineInt, err := strconv.Atoi(deadlineNum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains("mhd", deadlineUnit) {
|
||||||
|
return fmt.Errorf("invalid deadline unit '%s'. use one of: [ m | d | h ]", deadlineUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
var d time.Time
|
||||||
|
switch deadlineUnit {
|
||||||
|
case "m", "":
|
||||||
|
d = time.Now().UTC().Add(time.Duration(deadlineInt)*time.Minute).Round(time.Second)
|
||||||
|
case "h":
|
||||||
|
d = time.Now().UTC().Add(time.Duration(deadlineInt)*time.Hour).Round(time.Second)
|
||||||
|
case "d":
|
||||||
|
d = time.Now().UTC().Add(time.Duration(deadlineInt)*time.Hour*24).Round(time.Second)
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
q quorum.Quorum
|
||||||
|
t threshold.Threshold
|
||||||
|
)
|
||||||
|
if q, err = quorum.FromString(ctx.String("quorum")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t, err = threshold.FromString(ctx.String("threshold")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e := strings.Split(ctx.String("electors"), " ")
|
||||||
|
a := ctx.Bool("anonymous")
|
||||||
|
|
||||||
|
var r = ""
|
||||||
|
if ctx.Args().Len() == 0 {
|
||||||
|
fmt.Print("Give your referendum a concise name or subject: ")
|
||||||
|
inputReader := bufio.NewReader(os.Stdin)
|
||||||
|
r, _ = inputReader.ReadString('\n')
|
||||||
|
r = r[:len(r)-1]
|
||||||
|
} else {
|
||||||
|
r = strings.Join(ctx.Args().Slice(), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := utils.GenerateRandomString(11)
|
||||||
|
if err := store.NewVoting(string(id), r, d, q, t, e, a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(id))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
21
cmd/serve.go
Normal file
21
cmd/serve.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.c-base.org/baccenfutter/govote/http"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var serveCmd = &cli.Command{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "Start the HTTP server",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "bind-address",
|
||||||
|
Usage: "The TCP address:port to bind to",
|
||||||
|
Value: ":3000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
return http.Serve(ctx.String("bind-address"))
|
||||||
|
},
|
||||||
|
}
|
34
cmd/show.go
Normal file
34
cmd/show.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"code.c-base.org/baccenfutter/govote/store"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var showCmd = &cli.Command{
|
||||||
|
Name: "show",
|
||||||
|
Usage: "📈 Display a voting",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
var r string
|
||||||
|
if ctx.Args().Len() == 0 {
|
||||||
|
inputReader := bufio.NewReader(os.Stdin)
|
||||||
|
r, _ = inputReader.ReadString('\n')
|
||||||
|
r = r[:len(r)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ctx.Args().Get(0)
|
||||||
|
if id == "" {
|
||||||
|
return fmt.Errorf("Please provide an ID!")
|
||||||
|
}
|
||||||
|
voting, err := store.GetVoting(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(voting)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
46
cmd/vote.go
Normal file
46
cmd/vote.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.c-base.org/baccenfutter/govote/store"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/vote"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var voteCmd = &cli.Command{
|
||||||
|
Name: "vote",
|
||||||
|
Usage: "📄 Place vote",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "voting-id",
|
||||||
|
Usage: "Voting ID",
|
||||||
|
Aliases: []string{"V"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "elector",
|
||||||
|
Usage: "Elector",
|
||||||
|
Aliases: []string{"E"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "choice",
|
||||||
|
Usage: "Choice",
|
||||||
|
Aliases: []string{"C"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
id = uuid.New().String()
|
||||||
|
votingID = ctx.String("voting-id")
|
||||||
|
elector = ctx.String("elector")
|
||||||
|
)
|
||||||
|
choice, err := vote.ChoiceFromString(ctx.String("choice"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = store.PlaceVote(id, votingID, elector, choice)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
15
global.css
Normal file
15
global.css
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
html {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
div#__next {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
29
go.mod
Normal file
29
go.mod
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
module code.c-base.org/baccenfutter/govote
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
|
robpike.io/filter v0.0.0-20210831053821-dcb4225e6ac8
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
|
github.com/labstack/echo/v4 v4.12.0 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
)
|
51
go.sum
Normal file
51
go.sum
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||||
|
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
|
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||||
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||||
|
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
robpike.io/filter v0.0.0-20210831053821-dcb4225e6ac8 h1:lwm39gwvSdkVwepWIJM43BTc/1J7f6OCzMNUCox7ozI=
|
||||||
|
robpike.io/filter v0.0.0-20210831053821-dcb4225e6ac8/go.mod h1:1GvacT5fu9sizB4SqyrJVJk/CQN2ZN4Z/zld7fYxzLo=
|
137
http/main.go
Normal file
137
http/main.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.c-base.org/baccenfutter/govote/store"
|
||||||
|
"code.c-base.org/baccenfutter/govote/utils"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/quorum"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/threshold"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/vote"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"github.com/labstack/echo/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Serve(bindAddr string) error {
|
||||||
|
e := echo.New()
|
||||||
|
e.Pre(middleware.RemoveTrailingSlash())
|
||||||
|
// e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
NewTemplateRenderer(e, "tmpl/*.html")
|
||||||
|
|
||||||
|
e.Static("/static", "static")
|
||||||
|
e.GET("/", handleIndex)
|
||||||
|
e.GET("/v", handleVotingForm)
|
||||||
|
e.POST("/v", handleNewVoting)
|
||||||
|
e.GET("/v/:id", handleShowVoting)
|
||||||
|
e.POST("/v/:id", handleVote)
|
||||||
|
|
||||||
|
return e.Start(bindAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleIndex(ctx echo.Context) error {
|
||||||
|
// return ctx.Redirect(http.StatusTemporaryRedirect, "/v")
|
||||||
|
return ctx.String(200, ctx.Request().Header.Get("X-Remote-User"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNewVoting(ctx echo.Context) error {
|
||||||
|
id :=utils.GenerateRandomString(11)
|
||||||
|
var (
|
||||||
|
formReferendum = ctx.FormValue("referendum")
|
||||||
|
formDeadline = ctx.FormValue("deadline")
|
||||||
|
formQuorum = ctx.FormValue("quorum")
|
||||||
|
formThreshold = ctx.FormValue("threshold")
|
||||||
|
formElectors = ctx.FormValue("electors")
|
||||||
|
formAnonymous = ctx.FormValue("anonymous")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
r string
|
||||||
|
d time.Time
|
||||||
|
q quorum.Quorum
|
||||||
|
t threshold.Threshold
|
||||||
|
e = []string{}
|
||||||
|
a bool
|
||||||
|
)
|
||||||
|
|
||||||
|
r = formReferendum
|
||||||
|
deadlineNum := fmt.Sprintf("%s", formDeadline[:len(formDeadline)-1])
|
||||||
|
deadlineUnit := fmt.Sprintf("%s", formDeadline[len(formDeadline)-1:])
|
||||||
|
deadlineInt, err := strconv.Atoi(deadlineNum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch deadlineUnit {
|
||||||
|
case "m", "":
|
||||||
|
d = time.Now().UTC().Add(time.Duration(deadlineInt)*time.Minute).Round(time.Second)
|
||||||
|
case "h":
|
||||||
|
d = time.Now().UTC().Add(time.Duration(deadlineInt)*time.Hour).Round(time.Second)
|
||||||
|
case "d":
|
||||||
|
d = time.Now().UTC().Add(time.Duration(deadlineInt)*time.Hour*24).Round(time.Second)
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
if q, err = quorum.FromString(formQuorum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t, err = threshold.FromString(formThreshold); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e = strings.Split(formElectors, " ")
|
||||||
|
if formAnonymous == "on" {
|
||||||
|
a = true
|
||||||
|
}
|
||||||
|
|
||||||
|
store.NewVoting(id, r, d, q, t, e, a)
|
||||||
|
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/v/%s", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleVotingForm(ctx echo.Context) error {
|
||||||
|
return ctx.Render(http.StatusOK, "voting_form", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleVote(ctx echo.Context) error {
|
||||||
|
var (
|
||||||
|
id = uuid.New().String()
|
||||||
|
vid = ctx.Param("id")
|
||||||
|
elector = ctx.Request().Header.Get("X-Remote-User")
|
||||||
|
choice = ctx.FormValue("vote")
|
||||||
|
v *voting.Voting
|
||||||
|
c vote.Choice
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
v, err = store.GetVoting(vid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if time.Now().UTC().After(v.Deadline()) {
|
||||||
|
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/v/%s", vid))
|
||||||
|
}
|
||||||
|
if c, err = vote.ChoiceFromString(choice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.PlaceVote(id, vid, elector, c)
|
||||||
|
return ctx.Render(http.StatusFound, "thanks", map[string]interface{}{
|
||||||
|
"Id": id,
|
||||||
|
"Vid": vid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleShowVoting(ctx echo.Context) error {
|
||||||
|
v, err := store.GetVoting(ctx.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Render(http.StatusOK, "voting", map[string]interface{}{
|
||||||
|
"Voting": v,
|
||||||
|
})
|
||||||
|
}
|
31
http/template_renderer.go
Normal file
31
http/template_renderer.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
Templates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||||
|
return t.Templates.ExecuteTemplate(w, name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTemplateRenderer(e *echo.Echo, paths ...string) {
|
||||||
|
tmpl := &template.Template{}
|
||||||
|
for i := range paths {
|
||||||
|
template.Must(tmpl.ParseGlob(paths[i]))
|
||||||
|
}
|
||||||
|
t := newTemplate(tmpl)
|
||||||
|
e.Renderer = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTemplate(templates *template.Template) echo.Renderer {
|
||||||
|
return &Template{
|
||||||
|
Templates: templates,
|
||||||
|
}
|
||||||
|
}
|
19
main.go
Normal file
19
main.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"code.c-base.org/baccenfutter/govote/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate yarn run build
|
||||||
|
//go:generate cp node_modules/htmx.org/dist/htmx.min.js static/
|
||||||
|
//go:generate cp global.css static/css/
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cmd.App
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
14
package.json
Normal file
14
package.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "govote",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tailwindcss build -o static/css/tailwind.css",
|
||||||
|
"watch": "tailwindcss build -o static/css/tailwind.css --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"flowbite": "^2.3.0",
|
||||||
|
"tailwindcss": "^3.4.3"
|
||||||
|
}
|
||||||
|
}
|
15
static/css/global.css
Normal file
15
static/css/global.css
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
html {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
div#__next {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
1561
static/css/tailwind.css
Normal file
1561
static/css/tailwind.css
Normal file
File diff suppressed because it is too large
Load diff
1
static/htmx.min.js
vendored
Normal file
1
static/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
store/db.go
Normal file
21
store/db.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const file = "./govote.db"
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open("sqlite3", file); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initCreateTables(db)
|
||||||
|
initStmts(db)
|
||||||
|
}
|
||||||
|
|
110
store/handler.go
Normal file
110
store/handler.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/quorum"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/threshold"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/vote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewVoting(
|
||||||
|
id string,
|
||||||
|
r string,
|
||||||
|
d time.Time,
|
||||||
|
q quorum.Quorum,
|
||||||
|
t threshold.Threshold,
|
||||||
|
e []string,
|
||||||
|
a bool,
|
||||||
|
) error {
|
||||||
|
electors := strings.Join(e, ";")
|
||||||
|
if _, err := votingInsert.Exec(id, r, d.String(), q.String(), t.String(), electors, a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVoting(id string) (*voting.Voting, error) {
|
||||||
|
result := votingSelect.QueryRow(id)
|
||||||
|
if result == nil {
|
||||||
|
return nil, fmt.Errorf("not found: %s", id)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
r string
|
||||||
|
d time.Time
|
||||||
|
q quorum.Quorum
|
||||||
|
t threshold.Threshold
|
||||||
|
e []string
|
||||||
|
a bool
|
||||||
|
dbDeadline string
|
||||||
|
dbQuorum string
|
||||||
|
dbThreshold string
|
||||||
|
dbElectors string
|
||||||
|
)
|
||||||
|
if err := result.Scan(&r, &dbDeadline, &dbQuorum, &dbThreshold, &dbElectors, &a); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d, err = time.Parse("2006-01-02 15:04:05 -0700 MST", dbDeadline); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if q, err = quorum.FromString(dbQuorum); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t, err = threshold.FromString(dbThreshold); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e = strings.Split(dbElectors, " ")
|
||||||
|
|
||||||
|
votes, err := getVotes(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := voting.NewVotingWithVotes(id, r, d, q, t, e, a, votes)
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PlaceVote(id, votingID, elector string, choice vote.Choice) error {
|
||||||
|
if _, err := voteInsert.Exec(id, votingID, elector, choice.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVotes(id string) ([]vote.Vote, error) {
|
||||||
|
result, err := voteSelect.Query(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
e string
|
||||||
|
c vote.Choice
|
||||||
|
ts time.Time
|
||||||
|
dbChoice string
|
||||||
|
dbTimestamp string
|
||||||
|
votes = []vote.Vote{}
|
||||||
|
)
|
||||||
|
for result.Next() {
|
||||||
|
if err = result.Scan(&e, &dbChoice, &dbTimestamp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c, err = vote.ChoiceFromString(dbChoice); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts, err = time.Parse("2006-01-02 15:04:05", dbTimestamp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := vote.NewVoteWithTimestamp(e, c, ts)
|
||||||
|
votes = append(votes, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return votes, nil
|
||||||
|
}
|
136
store/prepared.go
Normal file
136
store/prepared.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import "database/sql"
|
||||||
|
|
||||||
|
var (
|
||||||
|
votingInsert *sql.Stmt
|
||||||
|
votingSelect *sql.Stmt
|
||||||
|
voteEligible *sql.Stmt
|
||||||
|
voteInsert *sql.Stmt
|
||||||
|
voteSelect *sql.Stmt
|
||||||
|
)
|
||||||
|
|
||||||
|
func initCreateTables(db *sql.DB) {
|
||||||
|
var err error
|
||||||
|
createTables := `
|
||||||
|
CREATE TABLE IF NOT EXISTS voting (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
referendum TEXT NOT NULL,
|
||||||
|
created DATETIME WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deadline DATETIME WITHOUT TIME ZONE NOT NULL,
|
||||||
|
quorum TEXT NOT NULL DEFAULT 'SIMPLE',
|
||||||
|
threshold TEXT NOT NULL DEFAULT 'SIMPLE',
|
||||||
|
electors TEXT,
|
||||||
|
anonymous BOOL NOT NULL DEFAULT false
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS vote (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
voting TEXT NOT NULL,
|
||||||
|
elector TEXT NOT NULL,
|
||||||
|
choice TEXT NOT NULL,
|
||||||
|
timestamp DATETIME WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY(voting) REFERENCES voting(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS vote_voting ON vote ( voting );
|
||||||
|
`
|
||||||
|
|
||||||
|
if _, err = db.Exec(createTables); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStmts(db *sql.DB) {
|
||||||
|
initStmtVotingInsert(db)
|
||||||
|
initStmtVotingSelect(db)
|
||||||
|
|
||||||
|
initStmtVoteEligible(db)
|
||||||
|
initStmtVoteInsert(db)
|
||||||
|
initStmtVoteSelect(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStmtVotingInsert(db *sql.DB) {
|
||||||
|
var err error
|
||||||
|
if votingInsert, err = db.Prepare(`
|
||||||
|
INSERT INTO voting (
|
||||||
|
id,
|
||||||
|
referendum,
|
||||||
|
deadline,
|
||||||
|
quorum,
|
||||||
|
threshold,
|
||||||
|
electors,
|
||||||
|
anonymous
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||||
|
`); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStmtVotingSelect(db *sql.DB) {
|
||||||
|
var err error
|
||||||
|
if votingSelect, err = db.Prepare(`
|
||||||
|
SELECT
|
||||||
|
referendum,
|
||||||
|
deadline,
|
||||||
|
quorum,
|
||||||
|
threshold,
|
||||||
|
electors,
|
||||||
|
anonymous
|
||||||
|
FROM voting
|
||||||
|
WHERE id = ?;
|
||||||
|
`); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStmtVoteEligible(db *sql.DB) {
|
||||||
|
var err error
|
||||||
|
if voteEligible, err = db.Prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
referendum,
|
||||||
|
created,
|
||||||
|
deadline,
|
||||||
|
quorum,
|
||||||
|
threshold
|
||||||
|
FROM voting
|
||||||
|
WHERE
|
||||||
|
id = ?
|
||||||
|
AND deadline > datetime('now')
|
||||||
|
AND (
|
||||||
|
electors IS NULL
|
||||||
|
OR ',' || electors || ',' LIKE '%,' || ? || ',%'
|
||||||
|
)
|
||||||
|
LIMIT 1;
|
||||||
|
`); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStmtVoteInsert(db *sql.DB) {
|
||||||
|
var err error
|
||||||
|
if voteInsert, err = db.Prepare(`
|
||||||
|
INSERT INTO vote (
|
||||||
|
id, voting, elector, choice
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?
|
||||||
|
);
|
||||||
|
`); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStmtVoteSelect(db *sql.DB) {
|
||||||
|
var err error
|
||||||
|
if voteSelect, err = db.Prepare(`
|
||||||
|
SELECT
|
||||||
|
elector,
|
||||||
|
choice,
|
||||||
|
timestamp
|
||||||
|
FROM vote
|
||||||
|
WHERE voting = ?;
|
||||||
|
`); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
13
tailwind.config.js
Normal file
13
tailwind.config.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
'./tmpl/*.html'
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('flowbite/plugin')
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
14
tmpl/head.html
Normal file
14
tmpl/head.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{{ define "head" }}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="/static/css/tailwind.css">
|
||||||
|
<script src="/static/htmx.min.js"></script>
|
||||||
|
|
||||||
|
<title>{{ or .Title "govote"}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
{{ end }}
|
5
tmpl/tail.html
Normal file
5
tmpl/tail.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{ define "tail" }}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
27
tmpl/thanks.html
Normal file
27
tmpl/thanks.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{{ define "thanks" }}
|
||||||
|
{{ template "head" . }}
|
||||||
|
<div class="flex flex-col justify-center align-center items-center h-screen bg-slate-900 p-2">
|
||||||
|
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-1/3 border rounded-lg p-2 sm:p-6 md:p-8 bg-slate-800 border-purple-900 text-white">
|
||||||
|
<div class="relative sm:rounded-lg">
|
||||||
|
<h1 class="text-center text-4xl text-white pb-2">
|
||||||
|
Thank you!
|
||||||
|
</h1>
|
||||||
|
<p class="p-4 text-gray-400 text-center">
|
||||||
|
You have made a small voting script very happy! *nom*
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 text-center">
|
||||||
|
Your vote has been placed under the ID:<br>
|
||||||
|
</p>
|
||||||
|
<p class="text-white text-center text-2xl bold">
|
||||||
|
{{ .Id }}
|
||||||
|
</p>
|
||||||
|
<hr class="m-4">
|
||||||
|
<form action="/v/{{ .Vid }}" method="GET">
|
||||||
|
<button type="submit" class="w-full hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center bg-purple-600 hover:bg-purple-700 focus:ring-blue-800 text-white hover:text-yellow-200 text-xl">Go back</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ template "tail" . }}
|
||||||
|
{{ end }}
|
||||||
|
|
121
tmpl/voting.html
Normal file
121
tmpl/voting.html
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
{{ define "voting" }}
|
||||||
|
{{ template "head" . }}
|
||||||
|
<div class="flex flex-col justify-center align-center items-center h-screen bg-slate-900 p-2">
|
||||||
|
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-1/3 border rounded-lg p-2 sm:p-6 md:p-8 bg-slate-800 border-purple-900 text-white">
|
||||||
|
<div class="relative sm:rounded-lg">
|
||||||
|
<h1 class="text-center text-4xl text-white pb-2">
|
||||||
|
{{ .Voting.Referendum }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-400 text-center text-xs">
|
||||||
|
{{ .Voting.Deadline }}
|
||||||
|
</p>
|
||||||
|
{{ if .Voting.IsOpen }}
|
||||||
|
{{ template "vote_form" . }}
|
||||||
|
{{ else }}
|
||||||
|
{{ template "voting_result" . }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ template "tail" . }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "vote_form" }}
|
||||||
|
<div class="relative overflow-x-auto py-4">
|
||||||
|
<table class="w-full text-sm text-left rtl:text-right text-gray-400 text-center">
|
||||||
|
<thead class="text-xs uppercase text-gray-400">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Quorum
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Threshold
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Anonymous
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="border-b bg-gray-800 border-gray-700">
|
||||||
|
<td scope="row" class="px-6 py-2 font-medium whitespace-nowrap text-white">
|
||||||
|
SIMPLE
|
||||||
|
</td>
|
||||||
|
<td scope="row" class="px-6 py-2 font-medium whitespace-nowrap text-white">
|
||||||
|
SIMPLE
|
||||||
|
</td>
|
||||||
|
<td scope="row" class="px-6 py-2 font-medium whitespace-nowrap text-white">
|
||||||
|
YES
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="/v/{{ .Voting.ID }}" method="POST">
|
||||||
|
<ul class="grid w-full gap-6 md:grid-cols-3 pb-8">
|
||||||
|
<li>
|
||||||
|
<input type="radio" id="vote-yes" name="vote" value="YIP" class="hidden peer" required />
|
||||||
|
<label for="vote-yes" class="inline-flex items-center justify-between w-full p-5 border rounded-lg cursor-pointer border-green-700 hover:border-gray-400 peer-checked:text-white peer-checked:bg-green-700 hover:text-white text-green-700">
|
||||||
|
<div class="block mx-auto">
|
||||||
|
<div class="w-full text-lg font-semibold">YIP</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" id="vote-no" name="vote" value="NOPE" class="hidden peer" required />
|
||||||
|
<label for="vote-no" class="inline-flex items-center justify-between w-full p-5 border rounded-lg cursor-pointer border-red-800 hover:border-gray-400 peer-checked:text-white peer-checked:bg-red-800 hover:text-white text-red-800">
|
||||||
|
<div class="block mx-auto">
|
||||||
|
<div class="w-full text-lg font-semibold">NOPE</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" id="vote-abstain" name="vote" value="DUNNO" class="hidden peer" required />
|
||||||
|
<label for="vote-abstain" class="inline-flex items-center justify-between w-full p-5 border rounded-lg cursor-pointer border-yellow-600 hover:border-gray-400 peer-checked:text-white peer-checked:bg-yellow-600 hover:text-white text-yellow-600">
|
||||||
|
<div class="block mx-auto">
|
||||||
|
<div class="w-full text-lg font-semibold">DUNNO</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button type="submit" class="w-full hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center bg-purple-600 hover:bg-purple-700 focus:ring-blue-800 text-white hover:text-yellow-200 text-xl">Vote</button>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "voting_result" }}
|
||||||
|
<table class="w-full text-sm text-left rtl:text-right text-gray-400 text-center mt-4">
|
||||||
|
<tbody>
|
||||||
|
<tr class="border-b border-t bg-gray-800 border-gray-700">
|
||||||
|
<td class="px-6 py-4 text-4xl bold text-green-700">
|
||||||
|
{{ .Voting.Result.Yes }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-4xl bold text-red-800">
|
||||||
|
{{ .Voting.Result.No }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-4xl bold text-yellow-700">
|
||||||
|
{{ .Voting.Result.Abstain }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{ if and .Voting.Result.Quorum .Voting.Result.Threshold }}
|
||||||
|
<p class="text-4xl bold text-center p-4 text-green-700 tracking-widest">
|
||||||
|
ACCEPTED
|
||||||
|
</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="text-4xl bold text-center p-4 text-red-800 tracking-widest">
|
||||||
|
REJECTED
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Voting.Result.Quorum }}
|
||||||
|
<p class="text-center text-green-700">
|
||||||
|
Quorum: REACHED
|
||||||
|
</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="text-center text-red-800">
|
||||||
|
Quorum: FAILED
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
84
tmpl/voting_form.html
Normal file
84
tmpl/voting_form.html
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
{{ define "voting_form" }}
|
||||||
|
{{ template "head" . }}
|
||||||
|
<div class="flex flex-col justify-center align-center items-center h-screen bg-slate-900 p-2">
|
||||||
|
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-1/3 border rounded-lg p-2 sm:p-6 md:p-8 bg-slate-800 border-purple-900">
|
||||||
|
<form class="space-y-6" action="/v" method="POST">
|
||||||
|
<h5 class="text-xl font-medium text-white">New Voting</h5>
|
||||||
|
<div>
|
||||||
|
<label for="referendum" class="block mb-2 text-sm font-medium text-white">Referendum</label>
|
||||||
|
<input type="text" name="referendum" id="referendum" class="border text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 bg-gray-600 border-gray-500 placeholder-gray-400 text-white" placeholder="Concise question or subject..." required autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row justify-between">
|
||||||
|
<div class="w-full px-2">
|
||||||
|
<label for="deadline" class="block mb-2 text-sm font-medium text-white">Deadline</label>
|
||||||
|
<input type="text" name="deadline" id="deadline" class="border text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 bg-gray-600 border-gray-500 placeholder-gray-400 text-white text-center" placeholder="Deadline, e.g.: 3m, 1h, 7d, etc" value="1m" required />
|
||||||
|
</div>
|
||||||
|
<div class="w-full px-2">
|
||||||
|
<label for="quorum" class="block mb-2 text-sm font-medium text-white">Quorum</label>
|
||||||
|
<select id="quorum" name="quorum" class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option selected>SIMPLE</option>
|
||||||
|
<option value="1/5">1/5</option>
|
||||||
|
<option value="1/4">1/4</option>
|
||||||
|
<option value="1/3">1/3</option>
|
||||||
|
<option value="1/2">1/2</option>
|
||||||
|
<option value="2/5">2/5</option>
|
||||||
|
<option value="2/3">2/3</option>
|
||||||
|
<option value="3/5">3/5</option>
|
||||||
|
<option value="3/4">3/4</option>
|
||||||
|
<option value="4/5">4/5</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="w-full px-2">
|
||||||
|
<label for="threshold" class="block mb-2 text-sm font-medium text-white">Threshold</label>
|
||||||
|
<select id="threshold" name="threshold" class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option selected>SIMPLE</option>
|
||||||
|
<option value="1/5">1/5</option>
|
||||||
|
<option value="1/4">1/4</option>
|
||||||
|
<option value="1/3">1/3</option>
|
||||||
|
<option value="1/2">1/2</option>
|
||||||
|
<option value="2/5">2/5</option>
|
||||||
|
<option value="2/3">2/3</option>
|
||||||
|
<option value="3/5">3/5</option>
|
||||||
|
<option value="3/4">3/4</option>
|
||||||
|
<option value="4/5">4/5</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="electors" class="block mb-2 text-sm font-medium text-white">Who can vote? <small>(empty if anyone can vote)</small></label>
|
||||||
|
<div class="flex">
|
||||||
|
<span class="inline-flex items-center px-3 text-sm border rounded-e-0 border-e-0 rounded-s-md bg-gray-600 text-gray-400 border-gray-600">
|
||||||
|
<svg class="w-4 h-4 text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="electores" name="electors" class="rounded-none rounded-e-lg border block flex-1 min-w-0 w-full text-sm border-gray-300 p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Names separated by spaces...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="inline-flex items-center cursor-pointer">
|
||||||
|
<input type="checkbox" value="anonymous" class="sr-only peer">
|
||||||
|
<div class="relative w-11 h-6 peer-focus:ring-purple-800 rounded-full peer bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-purple-600"></div>
|
||||||
|
<span class="ms-3 text-sm font-medium text-gray-400">Anonymous vote</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="w-full hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center bg-purple-600 hover:bg-purple-700 focus:ring-blue-800 text-white hover:text-yellow-200 text-xl">Create</button>
|
||||||
|
<p class="text-gray-400 italic">
|
||||||
|
Quotum:
|
||||||
|
The minumum number of eligible votes required for the referendum to pass.
|
||||||
|
(SIMPLE means that at least one vote is required.)
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 italic">
|
||||||
|
Threshold:
|
||||||
|
The minumum number of YES votes required for the referendum to pass.
|
||||||
|
(SIMPLE means that there must be more YES than NO votes.)
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 italic">
|
||||||
|
Anonymous:
|
||||||
|
Individual votes by the electors are not made public.
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ template "tail" . }}
|
||||||
|
{{ end }}
|
15
utils/random.go
Normal file
15
utils/random.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
|
func GenerateRandomString(length int) string {
|
||||||
|
result := make([]byte, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
result[i] = charSet[rand.Intn(len(charSet))]
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
203
voting/main.go
Normal file
203
voting/main.go
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
package voting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/quorum"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/threshold"
|
||||||
|
"code.c-base.org/baccenfutter/govote/voting/vote"
|
||||||
|
"robpike.io/filter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Voting struct {
|
||||||
|
id string
|
||||||
|
referendum string
|
||||||
|
deadline time.Time
|
||||||
|
quorum quorum.Quorum
|
||||||
|
threshold threshold.Threshold
|
||||||
|
electors []string
|
||||||
|
annonymous bool
|
||||||
|
votes []vote.Vote
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewVoting(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool) *Voting {
|
||||||
|
return &Voting{
|
||||||
|
id: id,
|
||||||
|
referendum: r,
|
||||||
|
deadline: d,
|
||||||
|
quorum: q,
|
||||||
|
threshold: t,
|
||||||
|
electors: e,
|
||||||
|
annonymous: a,
|
||||||
|
votes: []vote.Vote{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVotingWithVotes(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool, v []vote.Vote) *Voting {
|
||||||
|
voting := NewVoting(id, r, d, q, t, e, a)
|
||||||
|
for i := range v {
|
||||||
|
voting.votes = append(voting.votes, v[i])
|
||||||
|
}
|
||||||
|
return voting
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) String() string {
|
||||||
|
var (
|
||||||
|
possibleVotes int = len(v.electors)
|
||||||
|
totalVotes int = len(v.Votes())
|
||||||
|
yesVotes int = len(v.yesVotes())
|
||||||
|
noVotes int = len(v.noVotes())
|
||||||
|
deadlineStatus string = "🎭 ONGOING 🎭"
|
||||||
|
quorumStatus string = "❌ FAIL"
|
||||||
|
thresholdStatus string = "❌ FAIL"
|
||||||
|
out string = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
quorumSatisfied := v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||||
|
thresholdSatisfied := v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||||
|
votingSatisfied := quorumSatisfied && thresholdSatisfied
|
||||||
|
|
||||||
|
if time.Now().UTC().After(v.deadline) {
|
||||||
|
if votingSatisfied {
|
||||||
|
deadlineStatus = "✅ APROVED ✅"
|
||||||
|
} else {
|
||||||
|
deadlineStatus = "❌ REJECTED ❌"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.quorum.IsSatisfied(possibleVotes, totalVotes) {
|
||||||
|
quorumStatus = "✅ PASS"
|
||||||
|
}
|
||||||
|
if v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes) {
|
||||||
|
thresholdStatus = "✅ PASS"
|
||||||
|
}
|
||||||
|
|
||||||
|
out += fmt.Sprintf("Referendum: %s\n", strings.ToUpper(v.referendum))
|
||||||
|
out += fmt.Sprintf("Deadline : %s UTC\n", v.deadline.Format(time.DateTime))
|
||||||
|
out += fmt.Sprintf("Quorum : %s (%d/%d) (required: %s)\n", quorumStatus, totalVotes, possibleVotes, v.quorum)
|
||||||
|
out += fmt.Sprintf("Threshold : %s (%d/%d) (required: %s)\n", thresholdStatus, yesVotes, totalVotes, v.threshold)
|
||||||
|
out += fmt.Sprintf("Electors : [ %d ] %s\n", len(v.electors), v.electors)
|
||||||
|
out += fmt.Sprintf(
|
||||||
|
"Votes : [ %d | %d | %d ] (❎|❌|❔)\n",
|
||||||
|
len(v.yesVotes()),
|
||||||
|
len(v.noVotes()),
|
||||||
|
len(v.abstainVotes()),
|
||||||
|
)
|
||||||
|
out += fmt.Sprintf("Status : %s\n", deadlineStatus)
|
||||||
|
|
||||||
|
if !v.annonymous {
|
||||||
|
out += "\n"
|
||||||
|
if v.votes != nil && len(v.votes) > 0 {
|
||||||
|
for _, _v := range v.votes {
|
||||||
|
out += fmt.Sprintf("💬 %s\n", _v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) ID() string {
|
||||||
|
return v.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Referendum() string {
|
||||||
|
return v.referendum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Deadline() time.Time {
|
||||||
|
return v.deadline
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Quorum() string {
|
||||||
|
return v.quorum.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Threshold() string {
|
||||||
|
return v.threshold.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Electors() []string {
|
||||||
|
electors := make([]string, len(v.electors))
|
||||||
|
for i := range v.electors {
|
||||||
|
electors[i] = v.electors[i]
|
||||||
|
}
|
||||||
|
return electors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) IsOpen() bool {
|
||||||
|
return v.deadline.After(time.Now().UTC())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Result() Result {
|
||||||
|
var (
|
||||||
|
possibleVotes = len(v.electors)
|
||||||
|
totalVotes = len(v.Votes())
|
||||||
|
yesVotes = len(v.yesVotes())
|
||||||
|
noVotes = len(v.noVotes())
|
||||||
|
votes []vote.Vote
|
||||||
|
|
||||||
|
quorumSatisfied = v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||||
|
thresholdSatisfied = v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !v.annonymous {
|
||||||
|
votes = v.Votes()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{
|
||||||
|
Quorum: quorumSatisfied,
|
||||||
|
Threshold: thresholdSatisfied,
|
||||||
|
Yes: len(v.yesVotes()),
|
||||||
|
No: len(v.noVotes()),
|
||||||
|
Abstain: len(v.Votes()) - len(v.yesVotes()) - len(v.noVotes()),
|
||||||
|
Votes: votes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Voting) Vote(vote vote.Vote) error {
|
||||||
|
if time.Now().UTC().After(v.deadline) {
|
||||||
|
return fmt.Errorf("deadline has passed")
|
||||||
|
}
|
||||||
|
for _, elector := range v.electors {
|
||||||
|
if elector == vote.Elector {
|
||||||
|
v.votes = append(v.votes, vote)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not eligable to vote: %s", vote.Elector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) Votes() []vote.Vote {
|
||||||
|
votes := []vote.Vote{}
|
||||||
|
nextVote:
|
||||||
|
for i := len(v.votes)-1; i >= 0; i-- {
|
||||||
|
elector := v.votes[i].Elector
|
||||||
|
for _, e := range votes {
|
||||||
|
if e.Elector == elector {
|
||||||
|
continue nextVote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
votes = append(votes, v.votes[i])
|
||||||
|
}
|
||||||
|
return votes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) yesVotes() []vote.Vote {
|
||||||
|
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Yes }
|
||||||
|
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) noVotes() []vote.Vote {
|
||||||
|
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.No }
|
||||||
|
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Voting) abstainVotes() []vote.Vote {
|
||||||
|
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Abstain }
|
||||||
|
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||||
|
}
|
||||||
|
|
103
voting/quorum/quorum.go
Normal file
103
voting/quorum/quorum.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package quorum
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Quorum uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Simple Quorum = iota
|
||||||
|
OneFifth
|
||||||
|
OneQuarter
|
||||||
|
OneThird
|
||||||
|
OneHalf
|
||||||
|
TwoFifths
|
||||||
|
TwoThirds
|
||||||
|
ThreeQuarters
|
||||||
|
ThreeFifths
|
||||||
|
FourFifths
|
||||||
|
Unanimous
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidQuorums() []Quorum {
|
||||||
|
return []Quorum{
|
||||||
|
Simple,
|
||||||
|
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||||
|
TwoThirds, TwoFifths,
|
||||||
|
ThreeQuarters, ThreeFifths,
|
||||||
|
FourFifths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromString(s string) (Quorum, error) {
|
||||||
|
for _, q := range ValidQuorums() {
|
||||||
|
if strings.ToUpper(q.String()) == strings.ToUpper(s) {
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Simple, fmt.Errorf("inalid quorum: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Quorum) String() string {
|
||||||
|
switch q {
|
||||||
|
case Simple:
|
||||||
|
return "SIMPLE"
|
||||||
|
case OneFifth:
|
||||||
|
return "1/5"
|
||||||
|
case OneQuarter:
|
||||||
|
return "1/4"
|
||||||
|
case OneThird:
|
||||||
|
return "1/3"
|
||||||
|
case OneHalf:
|
||||||
|
return "1/2"
|
||||||
|
case TwoThirds:
|
||||||
|
return "2/3"
|
||||||
|
case TwoFifths:
|
||||||
|
return "2/5"
|
||||||
|
case ThreeQuarters:
|
||||||
|
return "3/4"
|
||||||
|
case ThreeFifths:
|
||||||
|
return "3/5"
|
||||||
|
case FourFifths:
|
||||||
|
return "4/5"
|
||||||
|
case Unanimous:
|
||||||
|
return "ALL"
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Quorum) IsSatisfied(possibleVotes, totalVotes int) bool {
|
||||||
|
if totalVotes == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch q {
|
||||||
|
case Simple:
|
||||||
|
return true
|
||||||
|
case OneFifth:
|
||||||
|
return totalVotes * 5 >= possibleVotes
|
||||||
|
case OneQuarter:
|
||||||
|
return totalVotes * 4 >= possibleVotes
|
||||||
|
case OneThird:
|
||||||
|
return totalVotes * 3 >= possibleVotes
|
||||||
|
case OneHalf:
|
||||||
|
return totalVotes * 2 >= possibleVotes
|
||||||
|
case TwoThirds:
|
||||||
|
return totalVotes * 3 >= possibleVotes * 2
|
||||||
|
case TwoFifths:
|
||||||
|
return totalVotes * 5 >= possibleVotes * 2
|
||||||
|
case ThreeQuarters:
|
||||||
|
return totalVotes * 4 >= possibleVotes * 3
|
||||||
|
case ThreeFifths:
|
||||||
|
return totalVotes * 5 >= possibleVotes * 3
|
||||||
|
case FourFifths:
|
||||||
|
return totalVotes * 5 >= possibleVotes * 4
|
||||||
|
case Unanimous:
|
||||||
|
return totalVotes >= possibleVotes
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached⚜️")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
10
voting/result.go
Normal file
10
voting/result.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package voting
|
||||||
|
|
||||||
|
import "code.c-base.org/baccenfutter/govote/voting/vote"
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Quorum bool
|
||||||
|
Threshold bool
|
||||||
|
Yes, No, Abstain int
|
||||||
|
Votes []vote.Vote
|
||||||
|
}
|
102
voting/threshold/threshold.go
Normal file
102
voting/threshold/threshold.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package threshold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Threshold uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Simple Threshold = iota
|
||||||
|
OneFifth
|
||||||
|
OneQuarter
|
||||||
|
OneThird
|
||||||
|
OneHalf
|
||||||
|
TwoThirds
|
||||||
|
TwoFifths
|
||||||
|
ThreeQuarters
|
||||||
|
ThreeFifths
|
||||||
|
FourFifths
|
||||||
|
Unanimous
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidThresholds() []Threshold {
|
||||||
|
return []Threshold{
|
||||||
|
Simple,
|
||||||
|
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||||
|
TwoFifths, TwoThirds,
|
||||||
|
ThreeFifths, ThreeQuarters, ThreeFifths,
|
||||||
|
FourFifths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromString(s string) (Threshold, error) {
|
||||||
|
for _, t := range ValidThresholds() {
|
||||||
|
if strings.ToUpper(t.String()) == strings.ToUpper(s) {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Simple, fmt.Errorf("invalid threshold: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Threshold) String() string {
|
||||||
|
switch t {
|
||||||
|
case Simple:
|
||||||
|
return "SIMPLE"
|
||||||
|
case OneFifth:
|
||||||
|
return "1/5"
|
||||||
|
case OneQuarter:
|
||||||
|
return "1/4"
|
||||||
|
case OneThird:
|
||||||
|
return "1/3"
|
||||||
|
case OneHalf:
|
||||||
|
return "1/2"
|
||||||
|
case TwoThirds:
|
||||||
|
return "2/3"
|
||||||
|
case TwoFifths:
|
||||||
|
return "2/5"
|
||||||
|
case ThreeQuarters:
|
||||||
|
return "3/4"
|
||||||
|
case ThreeFifths:
|
||||||
|
return "3/5"
|
||||||
|
case FourFifths:
|
||||||
|
return "4/5"
|
||||||
|
case Unanimous:
|
||||||
|
return "ALL"
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Threshold) IsSatisfied(totalVotes, yesVotes, noVotes int) bool {
|
||||||
|
if totalVotes == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case Simple:
|
||||||
|
return yesVotes > noVotes
|
||||||
|
case OneFifth:
|
||||||
|
return yesVotes * 5 >= totalVotes
|
||||||
|
case OneQuarter:
|
||||||
|
return yesVotes * 4 >= totalVotes
|
||||||
|
case OneThird:
|
||||||
|
return yesVotes * 3 >= totalVotes
|
||||||
|
case OneHalf:
|
||||||
|
return yesVotes * 2 >= totalVotes
|
||||||
|
case TwoThirds:
|
||||||
|
return yesVotes * 3 >= totalVotes * 2
|
||||||
|
case TwoFifths:
|
||||||
|
return yesVotes * 5 >= totalVotes * 2
|
||||||
|
case ThreeQuarters:
|
||||||
|
return yesVotes * 4 >= totalVotes * 3
|
||||||
|
case ThreeFifths:
|
||||||
|
return yesVotes * 5 >= totalVotes * 3
|
||||||
|
case FourFifths:
|
||||||
|
return yesVotes * 5 >= totalVotes * 4
|
||||||
|
case Unanimous:
|
||||||
|
return yesVotes >= totalVotes
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached")
|
||||||
|
}
|
||||||
|
}
|
40
voting/vote/choice.go
Normal file
40
voting/vote/choice.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package vote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Choice int8
|
||||||
|
|
||||||
|
func (choice Choice) String() string {
|
||||||
|
switch choice {
|
||||||
|
case Yes:
|
||||||
|
return "YIP"
|
||||||
|
case No:
|
||||||
|
return "NOPE"
|
||||||
|
case Abstain:
|
||||||
|
return "DUNNO"
|
||||||
|
default:
|
||||||
|
panic("this code should never be reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Abstain Choice = 0
|
||||||
|
Yes Choice = 1
|
||||||
|
No Choice = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidChoices() []Choice {
|
||||||
|
return []Choice{Yes, No, Abstain}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChoiceFromString(s string) (Choice, error) {
|
||||||
|
for _, c := range ValidChoices() {
|
||||||
|
if strings.ToUpper(c.String()) == strings.ToUpper(s) {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Abstain, fmt.Errorf("invalid choice: %s", s)
|
||||||
|
}
|
32
voting/vote/vote.go
Normal file
32
voting/vote/vote.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package vote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vote struct {
|
||||||
|
Elector string
|
||||||
|
Choice Choice
|
||||||
|
timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVote(elector string, choice Choice) Vote {
|
||||||
|
return Vote{
|
||||||
|
Elector: elector,
|
||||||
|
Choice: choice,
|
||||||
|
timestamp: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVoteWithTimestamp(elector string, choice Choice, timestamp time.Time) Vote {
|
||||||
|
return Vote{
|
||||||
|
Elector: elector,
|
||||||
|
Choice: choice,
|
||||||
|
timestamp: timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vote Vote) String() string {
|
||||||
|
return fmt.Sprintf("%s %s %s", vote.timestamp.Format(time.DateTime), vote.Choice, vote.Elector)
|
||||||
|
}
|
748
yarn.lock
Normal file
748
yarn.lock
Normal file
|
@ -0,0 +1,748 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@alloc/quick-lru@^5.2.0":
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
||||||
|
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
||||||
|
|
||||||
|
"@isaacs/cliui@^8.0.2":
|
||||||
|
version "8.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
|
||||||
|
integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
|
||||||
|
dependencies:
|
||||||
|
string-width "^5.1.2"
|
||||||
|
string-width-cjs "npm:string-width@^4.2.0"
|
||||||
|
strip-ansi "^7.0.1"
|
||||||
|
strip-ansi-cjs "npm:strip-ansi@^6.0.1"
|
||||||
|
wrap-ansi "^8.1.0"
|
||||||
|
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping@^0.3.2":
|
||||||
|
version "0.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
|
||||||
|
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/set-array" "^1.2.1"
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||||
|
"@jridgewell/trace-mapping" "^0.3.24"
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri@^3.1.0":
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
|
||||||
|
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||||
|
|
||||||
|
"@jridgewell/set-array@^1.2.1":
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
|
||||||
|
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
|
||||||
|
version "1.4.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
|
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping@^0.3.24":
|
||||||
|
version "0.3.25"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
|
||||||
|
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
|
version "2.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||||
|
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
|
||||||
|
dependencies:
|
||||||
|
"@nodelib/fs.stat" "2.0.5"
|
||||||
|
run-parallel "^1.1.9"
|
||||||
|
|
||||||
|
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||||
|
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||||
|
|
||||||
|
"@nodelib/fs.walk@^1.2.3":
|
||||||
|
version "1.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||||
|
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||||
|
dependencies:
|
||||||
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@pkgjs/parseargs@^0.11.0":
|
||||||
|
version "0.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||||
|
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||||
|
|
||||||
|
"@popperjs/core@^2.9.3":
|
||||||
|
version "2.11.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||||
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
|
|
||||||
|
ansi-regex@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||||
|
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||||
|
|
||||||
|
ansi-regex@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
|
||||||
|
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
|
||||||
|
|
||||||
|
ansi-styles@^4.0.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||||
|
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||||
|
dependencies:
|
||||||
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
|
ansi-styles@^6.1.0:
|
||||||
|
version "6.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||||
|
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||||
|
|
||||||
|
any-promise@^1.0.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||||
|
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
|
||||||
|
|
||||||
|
anymatch@~3.1.2:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||||
|
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
||||||
|
dependencies:
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
|
arg@^5.0.2:
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
|
||||||
|
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
|
||||||
|
|
||||||
|
balanced-match@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
|
binary-extensions@^2.0.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
|
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||||
|
|
||||||
|
brace-expansion@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||||
|
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^1.0.0"
|
||||||
|
|
||||||
|
braces@^3.0.2, braces@~3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||||
|
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||||
|
dependencies:
|
||||||
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
|
camelcase-css@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||||
|
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||||
|
|
||||||
|
chokidar@^3.5.3:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||||
|
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||||
|
dependencies:
|
||||||
|
anymatch "~3.1.2"
|
||||||
|
braces "~3.0.2"
|
||||||
|
glob-parent "~5.1.2"
|
||||||
|
is-binary-path "~2.1.0"
|
||||||
|
is-glob "~4.0.1"
|
||||||
|
normalize-path "~3.0.0"
|
||||||
|
readdirp "~3.6.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
color-convert@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||||
|
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
|
||||||
|
dependencies:
|
||||||
|
color-name "~1.1.4"
|
||||||
|
|
||||||
|
color-name@~1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
commander@^4.0.0:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||||
|
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||||
|
|
||||||
|
cross-spawn@^7.0.0:
|
||||||
|
version "7.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||||
|
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||||
|
dependencies:
|
||||||
|
path-key "^3.1.0"
|
||||||
|
shebang-command "^2.0.0"
|
||||||
|
which "^2.0.1"
|
||||||
|
|
||||||
|
cssesc@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||||
|
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||||
|
|
||||||
|
didyoumean@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||||
|
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
||||||
|
|
||||||
|
dlv@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
|
||||||
|
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
|
||||||
|
|
||||||
|
eastasianwidth@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||||
|
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||||
|
|
||||||
|
emoji-regex@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||||
|
|
||||||
|
emoji-regex@^9.2.2:
|
||||||
|
version "9.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||||
|
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||||
|
|
||||||
|
fast-glob@^3.3.0:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
||||||
|
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
|
||||||
|
dependencies:
|
||||||
|
"@nodelib/fs.stat" "^2.0.2"
|
||||||
|
"@nodelib/fs.walk" "^1.2.3"
|
||||||
|
glob-parent "^5.1.2"
|
||||||
|
merge2 "^1.3.0"
|
||||||
|
micromatch "^4.0.4"
|
||||||
|
|
||||||
|
fastq@^1.6.0:
|
||||||
|
version "1.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
|
||||||
|
integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
|
||||||
|
dependencies:
|
||||||
|
reusify "^1.0.4"
|
||||||
|
|
||||||
|
fill-range@^7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||||
|
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||||
|
dependencies:
|
||||||
|
to-regex-range "^5.0.1"
|
||||||
|
|
||||||
|
flowbite@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/flowbite/-/flowbite-2.3.0.tgz#0730e35d8b0d1dcdea26bb27d848bd9c0141cde1"
|
||||||
|
integrity sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==
|
||||||
|
dependencies:
|
||||||
|
"@popperjs/core" "^2.9.3"
|
||||||
|
mini-svg-data-uri "^1.4.3"
|
||||||
|
|
||||||
|
foreground-child@^3.1.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d"
|
||||||
|
integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^7.0.0"
|
||||||
|
signal-exit "^4.0.1"
|
||||||
|
|
||||||
|
fsevents@~2.3.2:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
|
||||||
|
function-bind@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||||
|
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||||
|
|
||||||
|
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
|
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
|
glob-parent@^6.0.2:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||||
|
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.3"
|
||||||
|
|
||||||
|
glob@^10.3.10:
|
||||||
|
version "10.3.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b"
|
||||||
|
integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==
|
||||||
|
dependencies:
|
||||||
|
foreground-child "^3.1.0"
|
||||||
|
jackspeak "^2.3.6"
|
||||||
|
minimatch "^9.0.1"
|
||||||
|
minipass "^7.0.4"
|
||||||
|
path-scurry "^1.10.2"
|
||||||
|
|
||||||
|
hasown@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||||
|
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
|
is-binary-path@~2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
|
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||||
|
dependencies:
|
||||||
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
|
is-core-module@^2.13.0:
|
||||||
|
version "2.13.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
|
||||||
|
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
|
||||||
|
dependencies:
|
||||||
|
hasown "^2.0.0"
|
||||||
|
|
||||||
|
is-extglob@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||||
|
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||||
|
|
||||||
|
is-fullwidth-code-point@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||||
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
|
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
|
dependencies:
|
||||||
|
is-extglob "^2.1.1"
|
||||||
|
|
||||||
|
is-number@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||||
|
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||||
|
|
||||||
|
isexe@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||||
|
|
||||||
|
jackspeak@^2.3.6:
|
||||||
|
version "2.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
|
||||||
|
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
|
||||||
|
dependencies:
|
||||||
|
"@isaacs/cliui" "^8.0.2"
|
||||||
|
optionalDependencies:
|
||||||
|
"@pkgjs/parseargs" "^0.11.0"
|
||||||
|
|
||||||
|
jiti@^1.21.0:
|
||||||
|
version "1.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
|
||||||
|
integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
|
||||||
|
|
||||||
|
lilconfig@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||||
|
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
||||||
|
|
||||||
|
lilconfig@^3.0.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3"
|
||||||
|
integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==
|
||||||
|
|
||||||
|
lines-and-columns@^1.1.6:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||||
|
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||||
|
|
||||||
|
lru-cache@^10.2.0:
|
||||||
|
version "10.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878"
|
||||||
|
integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==
|
||||||
|
|
||||||
|
merge2@^1.3.0:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
|
micromatch@^4.0.4, micromatch@^4.0.5:
|
||||||
|
version "4.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||||
|
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||||
|
dependencies:
|
||||||
|
braces "^3.0.2"
|
||||||
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
|
mini-svg-data-uri@^1.4.3:
|
||||||
|
version "1.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
|
||||||
|
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
|
||||||
|
|
||||||
|
minimatch@^9.0.1:
|
||||||
|
version "9.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51"
|
||||||
|
integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
|
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4:
|
||||||
|
version "7.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.0.tgz#b545f84af94e567386770159302ca113469c80b8"
|
||||||
|
integrity sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==
|
||||||
|
|
||||||
|
mz@^2.7.0:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
|
||||||
|
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
object-assign "^4.0.1"
|
||||||
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
|
nanoid@^3.3.7:
|
||||||
|
version "3.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||||
|
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||||
|
|
||||||
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
|
|
||||||
|
object-assign@^4.0.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
|
|
||||||
|
object-hash@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
||||||
|
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
||||||
|
|
||||||
|
path-key@^3.1.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||||
|
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||||
|
|
||||||
|
path-parse@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
|
path-scurry@^1.10.2:
|
||||||
|
version "1.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7"
|
||||||
|
integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^10.2.0"
|
||||||
|
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||||
|
|
||||||
|
picocolors@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
|
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||||
|
|
||||||
|
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
|
pify@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
|
integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
|
||||||
|
|
||||||
|
pirates@^4.0.1:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
|
||||||
|
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
|
||||||
|
|
||||||
|
postcss-import@^15.1.0:
|
||||||
|
version "15.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
|
||||||
|
integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
|
||||||
|
dependencies:
|
||||||
|
postcss-value-parser "^4.0.0"
|
||||||
|
read-cache "^1.0.0"
|
||||||
|
resolve "^1.1.7"
|
||||||
|
|
||||||
|
postcss-js@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
|
||||||
|
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
|
||||||
|
dependencies:
|
||||||
|
camelcase-css "^2.0.1"
|
||||||
|
|
||||||
|
postcss-load-config@^4.0.1:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3"
|
||||||
|
integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==
|
||||||
|
dependencies:
|
||||||
|
lilconfig "^3.0.0"
|
||||||
|
yaml "^2.3.4"
|
||||||
|
|
||||||
|
postcss-nested@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c"
|
||||||
|
integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==
|
||||||
|
dependencies:
|
||||||
|
postcss-selector-parser "^6.0.11"
|
||||||
|
|
||||||
|
postcss-selector-parser@^6.0.11:
|
||||||
|
version "6.0.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04"
|
||||||
|
integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==
|
||||||
|
dependencies:
|
||||||
|
cssesc "^3.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
postcss-value-parser@^4.0.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||||
|
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||||
|
|
||||||
|
postcss@^8.4.23:
|
||||||
|
version "8.4.38"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
||||||
|
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.7"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
|
queue-microtask@^1.2.2:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
read-cache@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||||
|
integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
|
||||||
|
dependencies:
|
||||||
|
pify "^2.3.0"
|
||||||
|
|
||||||
|
readdirp@~3.6.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||||
|
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||||
|
dependencies:
|
||||||
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
|
resolve@^1.1.7, resolve@^1.22.2:
|
||||||
|
version "1.22.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
|
||||||
|
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
||||||
|
dependencies:
|
||||||
|
is-core-module "^2.13.0"
|
||||||
|
path-parse "^1.0.7"
|
||||||
|
supports-preserve-symlinks-flag "^1.0.0"
|
||||||
|
|
||||||
|
reusify@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||||
|
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||||
|
|
||||||
|
run-parallel@^1.1.9:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||||
|
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
||||||
|
dependencies:
|
||||||
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
|
shebang-command@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||||
|
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
|
||||||
|
dependencies:
|
||||||
|
shebang-regex "^3.0.0"
|
||||||
|
|
||||||
|
shebang-regex@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||||
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
|
signal-exit@^4.0.1:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||||
|
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||||
|
|
||||||
|
source-map-js@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
|
||||||
|
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||||
|
|
||||||
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^5.0.1, string-width@^5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||||
|
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||||
|
dependencies:
|
||||||
|
eastasianwidth "^0.2.0"
|
||||||
|
emoji-regex "^9.2.2"
|
||||||
|
strip-ansi "^7.0.1"
|
||||||
|
|
||||||
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^7.0.1:
|
||||||
|
version "7.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
|
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^6.0.1"
|
||||||
|
|
||||||
|
sucrase@^3.32.0:
|
||||||
|
version "3.35.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
|
||||||
|
integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/gen-mapping" "^0.3.2"
|
||||||
|
commander "^4.0.0"
|
||||||
|
glob "^10.3.10"
|
||||||
|
lines-and-columns "^1.1.6"
|
||||||
|
mz "^2.7.0"
|
||||||
|
pirates "^4.0.1"
|
||||||
|
ts-interface-checker "^0.1.9"
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
|
tailwindcss@^3.4.3:
|
||||||
|
version "3.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.3.tgz#be48f5283df77dfced705451319a5dffb8621519"
|
||||||
|
integrity sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==
|
||||||
|
dependencies:
|
||||||
|
"@alloc/quick-lru" "^5.2.0"
|
||||||
|
arg "^5.0.2"
|
||||||
|
chokidar "^3.5.3"
|
||||||
|
didyoumean "^1.2.2"
|
||||||
|
dlv "^1.1.3"
|
||||||
|
fast-glob "^3.3.0"
|
||||||
|
glob-parent "^6.0.2"
|
||||||
|
is-glob "^4.0.3"
|
||||||
|
jiti "^1.21.0"
|
||||||
|
lilconfig "^2.1.0"
|
||||||
|
micromatch "^4.0.5"
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
object-hash "^3.0.0"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
postcss "^8.4.23"
|
||||||
|
postcss-import "^15.1.0"
|
||||||
|
postcss-js "^4.0.1"
|
||||||
|
postcss-load-config "^4.0.1"
|
||||||
|
postcss-nested "^6.0.1"
|
||||||
|
postcss-selector-parser "^6.0.11"
|
||||||
|
resolve "^1.22.2"
|
||||||
|
sucrase "^3.32.0"
|
||||||
|
|
||||||
|
thenify-all@^1.0.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||||
|
integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
|
||||||
|
dependencies:
|
||||||
|
thenify ">= 3.1.0 < 4"
|
||||||
|
|
||||||
|
"thenify@>= 3.1.0 < 4":
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
|
||||||
|
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
|
||||||
|
to-regex-range@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||||
|
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||||
|
dependencies:
|
||||||
|
is-number "^7.0.0"
|
||||||
|
|
||||||
|
ts-interface-checker@^0.1.9:
|
||||||
|
version "0.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
||||||
|
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||||
|
|
||||||
|
util-deprecate@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
|
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||||
|
|
||||||
|
which@^2.0.1:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||||
|
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||||
|
dependencies:
|
||||||
|
isexe "^2.0.0"
|
||||||
|
|
||||||
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^8.1.0:
|
||||||
|
version "8.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^6.1.0"
|
||||||
|
string-width "^5.0.1"
|
||||||
|
strip-ansi "^7.0.1"
|
||||||
|
|
||||||
|
yaml@^2.3.4:
|
||||||
|
version "2.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362"
|
||||||
|
integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==
|
Loading…
Reference in a new issue