diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69d2546..ac15f60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,38 +15,41 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + - name: Setup Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true + - name: Setup pnpm + run: | + corepack enable + corepack prepare pnpm@10.23.0 --activate + pnpm --version + - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends build-essential - - name: Go env - run: | - go version - go env + - name: pnpm format:check + run: pnpm -s format:check - - name: Test (default) + - name: pnpm lint + run: pnpm -s lint + + - name: pnpm test env: CGO_ENABLED: "1" - run: go test ./... + run: pnpm -s test - - name: Test (sqlite_fts5) + - name: pnpm build env: CGO_ENABLED: "1" - run: go test -tags sqlite_fts5 ./... - - - name: Build (default) - env: - CGO_ENABLED: "1" - run: go build ./cmd/wacli - - - name: Build (sqlite_fts5) - env: - CGO_ENABLED: "1" - run: go build -tags sqlite_fts5 ./cmd/wacli + run: pnpm -s build diff --git a/.gitignore b/.gitignore index 27d62dc..6ca6454 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ go.work.sum # Built CLI binary /wacli + +# pnpm build output +/dist/ diff --git a/cmd/wacli/auth.go b/cmd/wacli/auth.go index 264549a..3763b50 100644 --- a/cmd/wacli/auth.go +++ b/cmd/wacli/auth.go @@ -39,16 +39,16 @@ func newAuthCmd(flags *rootFlags) *cobra.Command { fmt.Fprintln(os.Stderr, "Starting authentication…") res, err := a.Sync(ctx, appPkg.SyncOptions{ - Mode: mode, - AllowQR: true, - DownloadMedia: downloadMedia, + Mode: mode, + AllowQR: true, + DownloadMedia: downloadMedia, RefreshContacts: true, RefreshGroups: true, - IdleExit: idleExit, + IdleExit: idleExit, OnQRCode: func(code string) { - fmt.Fprintln(os.Stderr, "\nScan this QR code with WhatsApp (Linked Devices):") - qrterminal.GenerateHalfBlock(code, qrterminal.M, os.Stderr) - fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "\nScan this QR code with WhatsApp (Linked Devices):") + qrterminal.GenerateHalfBlock(code, qrterminal.M, os.Stderr) + fmt.Fprintln(os.Stderr) }, }) if err != nil { diff --git a/cmd/wacli/helpers.go b/cmd/wacli/helpers.go index e12f5ed..bc5592e 100644 --- a/cmd/wacli/helpers.go +++ b/cmd/wacli/helpers.go @@ -38,4 +38,3 @@ func truncate(s string, max int) string { } return s[:max-1] + "…" } - diff --git a/cmd/wacli/main.go b/cmd/wacli/main.go index e8b1c9b..5b23f0b 100644 --- a/cmd/wacli/main.go +++ b/cmd/wacli/main.go @@ -9,4 +9,3 @@ func main() { os.Exit(1) } } - diff --git a/cmd/wacli/media.go b/cmd/wacli/media.go index 49d56e5..0967b4d 100644 --- a/cmd/wacli/media.go +++ b/cmd/wacli/media.go @@ -70,13 +70,13 @@ func newMediaDownloadCmd(flags *rootFlags) *cobra.Command { _ = a.DB().MarkMediaDownloaded(info.ChatJID, info.MsgID, target, now) resp := map[string]any{ - "chat": info.ChatJID, - "id": info.MsgID, - "path": target, - "bytes": bytes, - "media_type": info.MediaType, - "mime_type": info.MimeType, - "downloaded": true, + "chat": info.ChatJID, + "id": info.MsgID, + "path": target, + "bytes": bytes, + "media_type": info.MediaType, + "mime_type": info.MimeType, + "downloaded": true, "downloaded_at": now.Format(time.RFC3339Nano), } if flags.asJSON { diff --git a/cmd/wacli/root.go b/cmd/wacli/root.go index edadcb8..57fa200 100644 --- a/cmd/wacli/root.go +++ b/cmd/wacli/root.go @@ -74,9 +74,9 @@ func newApp(ctx context.Context, flags *rootFlags, needLock bool, allowUnauthed } a, err := app.New(app.Options{ - StoreDir: storeDir, - Version: version, - JSON: flags.asJSON, + StoreDir: storeDir, + Version: version, + JSON: flags.asJSON, AllowUnauthed: allowUnauthed, }) if err != nil { diff --git a/cmd/wacli/sync.go b/cmd/wacli/sync.go index c699bce..3bf1a97 100644 --- a/cmd/wacli/sync.go +++ b/cmd/wacli/sync.go @@ -48,12 +48,12 @@ func newSyncCmd(flags *rootFlags) *cobra.Command { } res, err := a.Sync(ctx, appPkg.SyncOptions{ - Mode: mode, - AllowQR: false, - DownloadMedia: downloadMedia, + Mode: mode, + AllowQR: false, + DownloadMedia: downloadMedia, RefreshContacts: refreshContacts, RefreshGroups: refreshGroups, - IdleExit: idleExit, + IdleExit: idleExit, }) if err != nil { return err diff --git a/cmd/wacli/version.go b/cmd/wacli/version.go index a0a4385..b8b048a 100644 --- a/cmd/wacli/version.go +++ b/cmd/wacli/version.go @@ -15,4 +15,3 @@ func newVersionCmd() *cobra.Command { }, } } - diff --git a/internal/app/app.go b/internal/app/app.go index cc6ceb7..b61285b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -76,10 +76,10 @@ func (a *App) EnsureAuthed() error { return fmt.Errorf("not authenticated; run `wacli auth`") } -func (a *App) WA() *wa.Client { return a.wa } -func (a *App) DB() *store.DB { return a.db } -func (a *App) StoreDir() string { return a.opts.StoreDir } -func (a *App) Version() string { return a.opts.Version } +func (a *App) WA() *wa.Client { return a.wa } +func (a *App) DB() *store.DB { return a.db } +func (a *App) StoreDir() string { return a.opts.StoreDir } +func (a *App) Version() string { return a.opts.Version } func (a *App) AllowUnauthed() bool { return a.opts.AllowUnauthed } func (a *App) Connect(ctx context.Context, allowQR bool, qrWriter func(string)) error { diff --git a/internal/app/bootstrap.go b/internal/app/bootstrap.go index 343ef59..1ddca9e 100644 --- a/internal/app/bootstrap.go +++ b/internal/app/bootstrap.go @@ -44,4 +44,3 @@ func (a *App) refreshGroups(ctx context.Context) error { } return nil } - diff --git a/internal/app/media.go b/internal/app/media.go index 3ac1cc1..6417aaf 100644 --- a/internal/app/media.go +++ b/internal/app/media.go @@ -140,4 +140,3 @@ func (a *App) downloadMediaJob(ctx context.Context, job mediaJob) error { now := time.Now().UTC() return a.db.MarkMediaDownloaded(info.ChatJID, info.MsgID, targetPath, now) } - diff --git a/internal/app/sync.go b/internal/app/sync.go index de7e804..e417818 100644 --- a/internal/app/sync.go +++ b/internal/app/sync.go @@ -23,14 +23,14 @@ const ( ) type SyncOptions struct { - Mode SyncMode - AllowQR bool - OnQRCode func(string) - DownloadMedia bool + Mode SyncMode + AllowQR bool + OnQRCode func(string) + DownloadMedia bool RefreshContacts bool RefreshGroups bool - IdleExit time.Duration // only used for bootstrap/once - Verbosity int // future + IdleExit time.Duration // only used for bootstrap/once + Verbosity int // future } type SyncResult struct { diff --git a/internal/config/config.go b/internal/config/config.go index 92a4fb9..dbd8eef 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,4 +12,3 @@ func DefaultStoreDir() string { } return filepath.Join(home, ".wacli") } - diff --git a/internal/out/out.go b/internal/out/out.go index eb0d903..1d795f6 100644 --- a/internal/out/out.go +++ b/internal/out/out.go @@ -34,4 +34,3 @@ func WriteError(w io.Writer, asJSON bool, err error) error { _, _ = fmt.Fprintln(w, err.Error()) return nil } - diff --git a/internal/pathutil/sanitize.go b/internal/pathutil/sanitize.go index 06e6dbf..91fab89 100644 --- a/internal/pathutil/sanitize.go +++ b/internal/pathutil/sanitize.go @@ -38,4 +38,3 @@ func SanitizeFilename(name string) string { name = strings.ReplaceAll(name, string(filepath.Separator), "_") return name } - diff --git a/internal/store/store.go b/internal/store/store.go index 1fc57cf..a8f4fbe 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -231,12 +231,12 @@ type Message struct { } type Contact struct { - JID string - Phone string - Name string - Alias string - Tags []string - UpdatedAt time.Time + JID string + Phone string + Name string + Alias string + Tags []string + UpdatedAt time.Time } func unix(t time.Time) int64 { @@ -276,23 +276,23 @@ func (d *DB) UpsertChat(jid, kind, name string, lastTS time.Time) error { } type UpsertMessageParams struct { - ChatJID string - ChatName string - MsgID string - SenderJID string - SenderName string - Timestamp time.Time - FromMe bool - Text string - MediaType string - MediaCaption string - Filename string - MimeType string - DirectPath string - MediaKey []byte - FileSHA256 []byte + ChatJID string + ChatName string + MsgID string + SenderJID string + SenderName string + Timestamp time.Time + FromMe bool + Text string + MediaType string + MediaCaption string + Filename string + MimeType string + DirectPath string + MediaKey []byte + FileSHA256 []byte FileEncSHA256 []byte - FileLength uint64 + FileLength uint64 } func (d *DB) UpsertMessage(p UpsertMessageParams) error { diff --git a/internal/wa/groups.go b/internal/wa/groups.go index 39947fa..c177b0e 100644 --- a/internal/wa/groups.go +++ b/internal/wa/groups.go @@ -91,4 +91,3 @@ func (c *Client) LeaveGroup(ctx context.Context, group types.JID) error { } return cli.LeaveGroup(ctx, group) } - diff --git a/internal/wa/media.go b/internal/wa/media.go index a8d9913..d5d55fc 100644 --- a/internal/wa/media.go +++ b/internal/wa/media.go @@ -85,4 +85,3 @@ func (c *Client) DownloadMediaToFile(ctx context.Context, directPath string, enc } return info.Size(), nil } - diff --git a/internal/wa/messages.go b/internal/wa/messages.go index efa57cc..b344579 100644 --- a/internal/wa/messages.go +++ b/internal/wa/messages.go @@ -163,4 +163,3 @@ func clone(b []byte) []byte { copy(out, b) return out } - diff --git a/package.json b/package.json new file mode 100644 index 0000000..afa6fd4 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "wacli", + "private": true, + "packageManager": "pnpm@10.23.0", + "scripts": { + "wacli": "go run -tags sqlite_fts5 ./cmd/wacli --", + "build": "mkdir -p dist && go build -tags sqlite_fts5 -o dist/wacli ./cmd/wacli", + "start": "pnpm -s build && ./dist/wacli", + "test": "pnpm -s test:go && pnpm -s test:fts", + "test:go": "go test ./...", + "test:fts": "go test -tags sqlite_fts5 ./...", + "lint": "go vet ./...", + "format": "gofmt -w .", + "format:check": "bash -lc 'out=$(gofmt -l .); if [ -n \"$out\" ]; then echo \"$out\"; exit 1; fi'" + } +}