From d5e11c23f6010708dde32d7774956fe31030a201 Mon Sep 17 00:00:00 2001 From: Sung <8265228+sungwoncho@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:20:18 -0700 Subject: [PATCH 1/5] Update self-hosting doc (#713) --- SELF_HOSTING.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md index 3638cb01..e67df891 100644 --- a/SELF_HOSTING.md +++ b/SELF_HOSTING.md @@ -6,27 +6,28 @@ Please see the [doc](https://www.getdnote.com/docs/server) for more. 1. Install [Docker](https://docs.docker.com/install/). 2. Install Docker [Compose plugin](https://docs.docker.com/compose/install/linux/). -3. Download the [compose.yml](https://raw.githubusercontent.com/dnote/dnote/master/host/docker/compose.yml) file by running: +3. Create a `compose.yml` file with the following content: -``` -curl https://raw.githubusercontent.com/dnote/dnote/master/host/docker/compose.yml > compose.yml +```yaml +services: + dnote: + image: dnote/dnote:latest + container_name: dnote + ports: + - 3001:3001 + volumes: + - ./dnote_data:/data + restart: unless-stopped ``` -4. Run the following to download the images and run the containers +4. Run the following to download the image and start the container ``` -docker compose pull docker compose up -d ``` Visit http://localhost:3001 in your browser to see Dnote running. -### Supported platform - -Currently, the official Docker image for Dnote supports Linux running AMD64 CPU architecture. - -If you run ARM64, please install Dnote server by downloading a binary distribution (see below). - ## Manual Installation Download from [releases](https://github.com/dnote/dnote/releases), extract, and run: @@ -34,7 +35,7 @@ Download from [releases](https://github.com/dnote/dnote/releases), extract, and ```bash tar -xzf dnote-server-$version-$os.tar.gz mv ./dnote-server /usr/local/bin -dnote-server start --webUrl=https://your.server +dnote-server start --baseUrl=https://your.server ``` You're up and running. Database: `~/.local/share/dnote/server.db` (customize with `--dbPath`). Run `dnote-server start --help` for options. From 5c416e3a327e55d9b20b987611e6cd032243008b Mon Sep 17 00:00:00 2001 From: Sung <8265228+sungwoncho@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:59:51 -0700 Subject: [PATCH 2/5] Add user list command (#714) --- pkg/e2e/server_test.go | 20 +++++++++++ pkg/server/app/users.go | 11 ++++++ pkg/server/app/users_test.go | 66 ++++++++++++++++++++++++++++++++++++ pkg/server/cmd/helpers.go | 4 +-- pkg/server/cmd/user.go | 31 +++++++++++++++-- pkg/server/cmd/user_test.go | 49 ++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 5 deletions(-) diff --git a/pkg/e2e/server_test.go b/pkg/e2e/server_test.go index 7c646e8d..e8ca3da6 100644 --- a/pkg/e2e/server_test.go +++ b/pkg/e2e/server_test.go @@ -331,3 +331,23 @@ func TestServerUserCreateHelp(t *testing.T) { assert.Equal(t, strings.Contains(outputStr, "--password"), true, "help should show --password (double dash)") assert.Equal(t, strings.Contains(outputStr, "--dbPath"), true, "help should show --dbPath (double dash)") } + +func TestServerUserList(t *testing.T) { + tmpDB := t.TempDir() + "/test.db" + + // Create two users + exec.Command(testServerBinary, "user", "create", "--dbPath", tmpDB, "--email", "alice@example.com", "--password", "password123").CombinedOutput() + exec.Command(testServerBinary, "user", "create", "--dbPath", tmpDB, "--email", "bob@example.com", "--password", "password123").CombinedOutput() + + // List users + listCmd := exec.Command(testServerBinary, "user", "list", "--dbPath", tmpDB) + output, err := listCmd.CombinedOutput() + + if err != nil { + t.Fatalf("user list failed: %v\nOutput: %s", err, output) + } + + outputStr := string(output) + assert.Equal(t, strings.Contains(outputStr, "alice@example.com"), true, "output should have alice") + assert.Equal(t, strings.Contains(outputStr, "bob@example.com"), true, "output should have bob") +} diff --git a/pkg/server/app/users.go b/pkg/server/app/users.go index 760b3354..9c167b69 100644 --- a/pkg/server/app/users.go +++ b/pkg/server/app/users.go @@ -116,6 +116,17 @@ func (a *App) GetUserByEmail(email string) (*database.User, error) { return &user, nil } +// GetAllUsers retrieves all users from the database +func (a *App) GetAllUsers() ([]database.User, error) { + var users []database.User + err := a.DB.Find(&users).Error + if err != nil { + return nil, pkgErrors.Wrap(err, "finding users") + } + + return users, nil +} + // Authenticate authenticates a user func (a *App) Authenticate(email, password string) (*database.User, error) { user, err := a.GetUserByEmail(email) diff --git a/pkg/server/app/users_test.go b/pkg/server/app/users_test.go index f345b217..52183520 100644 --- a/pkg/server/app/users_test.go +++ b/pkg/server/app/users_test.go @@ -108,6 +108,72 @@ func TestGetUserByEmail(t *testing.T) { }) } +func TestGetAllUsers(t *testing.T) { + t.Run("success with multiple users", func(t *testing.T) { + db := testutils.InitMemoryDB(t) + + user1 := testutils.SetupUserData(db, "alice@example.com", "password123") + user2 := testutils.SetupUserData(db, "bob@example.com", "password123") + user3 := testutils.SetupUserData(db, "charlie@example.com", "password123") + + a := NewTest() + a.DB = db + + users, err := a.GetAllUsers() + + assert.Equal(t, err, nil, "should not error") + assert.Equal(t, len(users), 3, "should return 3 users") + + // Verify all users are returned + emails := make(map[string]bool) + for _, user := range users { + emails[user.Email.String] = true + } + assert.Equal(t, emails["alice@example.com"], true, "alice should be in results") + assert.Equal(t, emails["bob@example.com"], true, "bob should be in results") + assert.Equal(t, emails["charlie@example.com"], true, "charlie should be in results") + + // Verify user details match + for _, user := range users { + if user.Email.String == "alice@example.com" { + assert.Equal(t, user.ID, user1.ID, "alice ID mismatch") + } else if user.Email.String == "bob@example.com" { + assert.Equal(t, user.ID, user2.ID, "bob ID mismatch") + } else if user.Email.String == "charlie@example.com" { + assert.Equal(t, user.ID, user3.ID, "charlie ID mismatch") + } + } + }) + + t.Run("empty database", func(t *testing.T) { + db := testutils.InitMemoryDB(t) + + a := NewTest() + a.DB = db + + users, err := a.GetAllUsers() + + assert.Equal(t, err, nil, "should not error") + assert.Equal(t, len(users), 0, "should return 0 users") + }) + + t.Run("single user", func(t *testing.T) { + db := testutils.InitMemoryDB(t) + + user := testutils.SetupUserData(db, "alice@example.com", "password123") + + a := NewTest() + a.DB = db + + users, err := a.GetAllUsers() + + assert.Equal(t, err, nil, "should not error") + assert.Equal(t, len(users), 1, "should return 1 user") + assert.Equal(t, users[0].Email.String, "alice@example.com", "email mismatch") + assert.Equal(t, users[0].ID, user.ID, "user ID mismatch") + }) +} + func TestCreateUser(t *testing.T) { t.Run("success", func(t *testing.T) { db := testutils.InitMemoryDB(t) diff --git a/pkg/server/cmd/helpers.go b/pkg/server/cmd/helpers.go index 2a322476..ba90a7ce 100644 --- a/pkg/server/cmd/helpers.go +++ b/pkg/server/cmd/helpers.go @@ -111,8 +111,8 @@ func requireString(fs *flag.FlagSet, value, fieldName string) { } } -// setupAppWithDB creates config, initializes app, and returns cleanup function -func setupAppWithDB(fs *flag.FlagSet, dbPath string) (*app.App, func()) { +// createApp creates config, initializes app, and returns cleanup function +func createApp(fs *flag.FlagSet, dbPath string) (*app.App, func()) { cfg, err := config.New(config.Params{ DBPath: dbPath, }) diff --git a/pkg/server/cmd/user.go b/pkg/server/cmd/user.go index 01b753b1..7a98344d 100644 --- a/pkg/server/cmd/user.go +++ b/pkg/server/cmd/user.go @@ -51,7 +51,7 @@ func userCreateCmd(args []string) { requireString(fs, *email, "email") requireString(fs, *password, "password") - a, cleanup := setupAppWithDB(fs, *dbPath) + a, cleanup := createApp(fs, *dbPath) defer cleanup() _, err := a.CreateUser(*email, *password, *password) @@ -74,7 +74,7 @@ func userRemoveCmd(args []string, stdin io.Reader) { requireString(fs, *email, "email") - a, cleanup := setupAppWithDB(fs, *dbPath) + a, cleanup := createApp(fs, *dbPath) defer cleanup() // Check if user exists first @@ -127,7 +127,7 @@ func userResetPasswordCmd(args []string) { requireString(fs, *email, "email") requireString(fs, *password, "password") - a, cleanup := setupAppWithDB(fs, *dbPath) + a, cleanup := createApp(fs, *dbPath) defer cleanup() // Find the user @@ -151,6 +151,27 @@ func userResetPasswordCmd(args []string) { fmt.Printf("Email: %s\n", *email) } +func userListCmd(args []string, output io.Writer) { + fs := setupFlagSet("list", "dnote-server user list") + + dbPath := fs.String("dbPath", "", "Path to SQLite database file (env: DBPath, default: $XDG_DATA_HOME/dnote/server.db)") + + fs.Parse(args) + + a, cleanup := createApp(fs, *dbPath) + defer cleanup() + + users, err := a.GetAllUsers() + if err != nil { + log.ErrorWrap(err, "listing users") + os.Exit(1) + } + + for _, user := range users { + fmt.Fprintf(output, "%s,%s,%s\n", user.UUID, user.Email.String, user.CreatedAt.UTC().Format("2006-01-02T15:04:05Z")) + } +} + func userCmd(args []string) { if len(args) < 1 { fmt.Println(`Usage: @@ -158,6 +179,7 @@ func userCmd(args []string) { Available commands: create: Create a new user + list: List all users remove: Remove a user reset-password: Reset a user's password`) os.Exit(1) @@ -172,6 +194,8 @@ Available commands: switch subcommand { case "create": userCreateCmd(subArgs) + case "list": + userListCmd(subArgs, os.Stdout) case "remove": userRemoveCmd(subArgs, os.Stdin) case "reset-password": @@ -180,6 +204,7 @@ Available commands: fmt.Printf("Unknown subcommand: %s\n\n", subcommand) fmt.Println(`Available commands: create: Create a new user + list: List all users remove: Remove a user (only if they have no notes or books) reset-password: Reset a user's password`) os.Exit(1) diff --git a/pkg/server/cmd/user_test.go b/pkg/server/cmd/user_test.go index 834ae3bd..84e5f4de 100644 --- a/pkg/server/cmd/user_test.go +++ b/pkg/server/cmd/user_test.go @@ -16,6 +16,8 @@ package cmd import ( + "bytes" + "fmt" "strings" "testing" @@ -107,3 +109,50 @@ func TestUserResetPasswordCmd(t *testing.T) { err = bcrypt.CompareHashAndPassword([]byte(updatedUser.Password.String), []byte("oldpassword123")) assert.Equal(t, err != nil, true, "old password should not match") } + +func TestUserListCmd(t *testing.T) { + t.Run("multiple users", func(t *testing.T) { + tmpDB := t.TempDir() + "/test.db" + + // Create multiple users + db := testutils.InitDB(tmpDB) + user1 := testutils.SetupUserData(db, "alice@example.com", "password123") + user2 := testutils.SetupUserData(db, "bob@example.com", "password123") + user3 := testutils.SetupUserData(db, "charlie@example.com", "password123") + sqlDB, _ := db.DB() + sqlDB.Close() + + // Capture output + var buf bytes.Buffer + userListCmd([]string{"--dbPath", tmpDB}, &buf) + + // Verify output matches expected format + output := strings.TrimSpace(buf.String()) + lines := strings.Split(output, "\n") + + expectedLine1 := fmt.Sprintf("%s,alice@example.com,%s", user1.UUID, user1.CreatedAt.UTC().Format("2006-01-02T15:04:05Z")) + expectedLine2 := fmt.Sprintf("%s,bob@example.com,%s", user2.UUID, user2.CreatedAt.UTC().Format("2006-01-02T15:04:05Z")) + expectedLine3 := fmt.Sprintf("%s,charlie@example.com,%s", user3.UUID, user3.CreatedAt.UTC().Format("2006-01-02T15:04:05Z")) + + assert.Equal(t, lines[0], expectedLine1, "line 1 should match") + assert.Equal(t, lines[1], expectedLine2, "line 2 should match") + assert.Equal(t, lines[2], expectedLine3, "line 3 should match") + }) + + t.Run("empty database", func(t *testing.T) { + tmpDB := t.TempDir() + "/test.db" + + // Initialize empty database + db := testutils.InitDB(tmpDB) + sqlDB, _ := db.DB() + sqlDB.Close() + + // Capture output + var buf bytes.Buffer + userListCmd([]string{"--dbPath", tmpDB}, &buf) + + // Verify no output + output := buf.String() + assert.Equal(t, output, "", "should have no output for empty database") + }) +} From 8f37d34df6712295bd57f9711189abd143d4765b Mon Sep 17 00:00:00 2001 From: Sung <8265228+sungwoncho@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:53:41 -0800 Subject: [PATCH 3/5] Remove ls and cat commands (#715) --- pkg/cli/cmd/add/add.go | 2 +- pkg/cli/cmd/cat/cat.go | 95 ------------- pkg/cli/cmd/edit/note.go | 2 +- pkg/cli/cmd/remove/remove.go | 3 +- pkg/cli/cmd/{ls/ls.go => view/book.go} | 89 ++---------- pkg/cli/cmd/view/book_test.go | 184 +++++++++++++++++++++++++ pkg/cli/cmd/view/note.go | 47 +++++++ pkg/cli/cmd/view/note_test.go | 90 ++++++++++++ pkg/cli/cmd/view/view.go | 26 ++-- pkg/cli/main.go | 4 - pkg/cli/main_test.go | 63 +++++++++ pkg/cli/output/output.go | 13 +- pkg/cli/testutils/main.go | 4 +- 13 files changed, 425 insertions(+), 197 deletions(-) delete mode 100644 pkg/cli/cmd/cat/cat.go rename pkg/cli/cmd/{ls/ls.go => view/book.go} (63%) create mode 100644 pkg/cli/cmd/view/book_test.go create mode 100644 pkg/cli/cmd/view/note.go create mode 100644 pkg/cli/cmd/view/note_test.go diff --git a/pkg/cli/cmd/add/add.go b/pkg/cli/cmd/add/add.go index be89b829..3e6d089d 100644 --- a/pkg/cli/cmd/add/add.go +++ b/pkg/cli/cmd/add/add.go @@ -131,7 +131,7 @@ func newRun(ctx context.DnoteCtx) infra.RunEFunc { return err } - output.NoteInfo(info) + output.NoteInfo(os.Stdout, info) if err := upgrade.Check(ctx); err != nil { log.Error(errors.Wrap(err, "automatically checking updates").Error()) diff --git a/pkg/cli/cmd/cat/cat.go b/pkg/cli/cmd/cat/cat.go deleted file mode 100644 index c32eb687..00000000 --- a/pkg/cli/cmd/cat/cat.go +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright 2025 Dnote Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cat - -import ( - "strconv" - - "github.com/dnote/dnote/pkg/cli/context" - "github.com/dnote/dnote/pkg/cli/database" - "github.com/dnote/dnote/pkg/cli/infra" - "github.com/dnote/dnote/pkg/cli/log" - "github.com/dnote/dnote/pkg/cli/output" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var example = ` - * See the notes with index 2 from a book 'javascript' - dnote cat javascript 2 - ` - -var deprecationWarning = `and "view" will replace it in the future version. - - Run "dnote view --help" for more information. -` - -func preRun(cmd *cobra.Command, args []string) error { - if len(args) != 2 { - return errors.New("Incorrect number of arguments") - } - - return nil -} - -// NewCmd returns a new cat command -func NewCmd(ctx context.DnoteCtx) *cobra.Command { - cmd := &cobra.Command{ - Use: "cat ", - Aliases: []string{"c"}, - Short: "See a note", - Example: example, - RunE: NewRun(ctx, false), - PreRunE: preRun, - Deprecated: deprecationWarning, - } - - return cmd -} - -// NewRun returns a new run function -func NewRun(ctx context.DnoteCtx, contentOnly bool) infra.RunEFunc { - return func(cmd *cobra.Command, args []string) error { - var noteRowIDArg string - - if len(args) == 2 { - log.Plain(log.ColorYellow.Sprintf("DEPRECATED: you no longer need to pass book name to the view command. e.g. `dnote view 123`.\n\n")) - - noteRowIDArg = args[1] - } else { - noteRowIDArg = args[0] - } - - noteRowID, err := strconv.Atoi(noteRowIDArg) - if err != nil { - return errors.Wrap(err, "invalid rowid") - } - - db := ctx.DB - info, err := database.GetNoteInfo(db, noteRowID) - if err != nil { - return err - } - - if contentOnly { - output.NoteContent(info) - } else { - output.NoteInfo(info) - } - - return nil - } -} diff --git a/pkg/cli/cmd/edit/note.go b/pkg/cli/cmd/edit/note.go index 84a631f4..cb837e11 100644 --- a/pkg/cli/cmd/edit/note.go +++ b/pkg/cli/cmd/edit/note.go @@ -166,7 +166,7 @@ func runNote(ctx context.DnoteCtx, rowIDArg string) error { } log.Success("edited the note\n") - output.NoteInfo(noteInfo) + output.NoteInfo(os.Stdout, noteInfo) return nil } diff --git a/pkg/cli/cmd/remove/remove.go b/pkg/cli/cmd/remove/remove.go index 89d44b78..18224c0f 100644 --- a/pkg/cli/cmd/remove/remove.go +++ b/pkg/cli/cmd/remove/remove.go @@ -17,6 +17,7 @@ package remove import ( "fmt" + "os" "strconv" "github.com/dnote/dnote/pkg/cli/context" @@ -129,7 +130,7 @@ func runNote(ctx context.DnoteCtx, rowIDArg string) error { return err } - output.NoteInfo(noteInfo) + output.NoteInfo(os.Stdout, noteInfo) ok, err := maybeConfirm("remove this note?", false) if err != nil { diff --git a/pkg/cli/cmd/ls/ls.go b/pkg/cli/cmd/view/book.go similarity index 63% rename from pkg/cli/cmd/ls/ls.go rename to pkg/cli/cmd/view/book.go index f0ddd047..698a7de5 100644 --- a/pkg/cli/cmd/ls/ls.go +++ b/pkg/cli/cmd/view/book.go @@ -13,76 +13,19 @@ * limitations under the License. */ -package ls +package view import ( "database/sql" "fmt" + "io" "strings" "github.com/dnote/dnote/pkg/cli/context" - "github.com/dnote/dnote/pkg/cli/infra" "github.com/dnote/dnote/pkg/cli/log" "github.com/pkg/errors" - "github.com/spf13/cobra" ) -var example = ` - * List all books - dnote ls - - * List notes in a book - dnote ls javascript - ` - -var deprecationWarning = `and "view" will replace it in the future version. - -Run "dnote view --help" for more information. -` - -func preRun(cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return errors.New("Incorrect number of argument") - } - - return nil -} - -// NewCmd returns a new ls command -func NewCmd(ctx context.DnoteCtx) *cobra.Command { - cmd := &cobra.Command{ - Use: "ls ", - Aliases: []string{"l", "notes"}, - Short: "List all notes", - Example: example, - RunE: NewRun(ctx, false), - PreRunE: preRun, - Deprecated: deprecationWarning, - } - - return cmd -} - -// NewRun returns a new run function for ls -func NewRun(ctx context.DnoteCtx, nameOnly bool) infra.RunEFunc { - return func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - if err := printBooks(ctx, nameOnly); err != nil { - return errors.Wrap(err, "viewing books") - } - - return nil - } - - bookName := args[0] - if err := printNotes(ctx, bookName); err != nil { - return errors.Wrapf(err, "viewing book '%s'", bookName) - } - - return nil - } -} - // bookInfo is an information about the book to be printed on screen type bookInfo struct { BookLabel string @@ -97,15 +40,13 @@ type noteInfo struct { // getNewlineIdx returns the index of newline character in a string func getNewlineIdx(str string) int { - var ret int - - ret = strings.Index(str, "\n") - - if ret == -1 { - ret = strings.Index(str, "\r\n") + // Check for \r\n first + if idx := strings.Index(str, "\r\n"); idx != -1 { + return idx } - return ret + // Then check for \n + return strings.Index(str, "\n") } // formatBody returns an excerpt of the given raw note content and a boolean @@ -123,15 +64,15 @@ func formatBody(noteBody string) (string, bool) { return strings.Trim(trimmed, " "), false } -func printBookLine(info bookInfo, nameOnly bool) { +func printBookLine(w io.Writer, info bookInfo, nameOnly bool) { if nameOnly { - fmt.Println(info.BookLabel) + fmt.Fprintln(w, info.BookLabel) } else { - log.Printf("%s %s\n", info.BookLabel, log.ColorYellow.Sprintf("(%d)", info.NoteCount)) + fmt.Fprintf(w, "%s %s\n", info.BookLabel, log.ColorYellow.Sprintf("(%d)", info.NoteCount)) } } -func printBooks(ctx context.DnoteCtx, nameOnly bool) error { +func listBooks(ctx context.DnoteCtx, w io.Writer, nameOnly bool) error { db := ctx.DB rows, err := db.Query(`SELECT books.label, count(notes.uuid) note_count @@ -157,13 +98,13 @@ func printBooks(ctx context.DnoteCtx, nameOnly bool) error { } for _, info := range infos { - printBookLine(info, nameOnly) + printBookLine(w, info, nameOnly) } return nil } -func printNotes(ctx context.DnoteCtx, bookName string) error { +func listNotes(ctx context.DnoteCtx, w io.Writer, bookName string) error { db := ctx.DB var bookUUID string @@ -191,7 +132,7 @@ func printNotes(ctx context.DnoteCtx, bookName string) error { infos = append(infos, info) } - log.Infof("on book %s\n", bookName) + fmt.Fprintf(w, "on book %s\n", bookName) for _, info := range infos { body, isExcerpt := formatBody(info.Body) @@ -201,7 +142,7 @@ func printNotes(ctx context.DnoteCtx, bookName string) error { body = fmt.Sprintf("%s %s", body, log.ColorYellow.Sprintf("[---More---]")) } - log.Plainf("%s %s\n", rowid, body) + fmt.Fprintf(w, "%s %s\n", rowid, body) } return nil diff --git a/pkg/cli/cmd/view/book_test.go b/pkg/cli/cmd/view/book_test.go new file mode 100644 index 00000000..226d5d04 --- /dev/null +++ b/pkg/cli/cmd/view/book_test.go @@ -0,0 +1,184 @@ +/* Copyright 2025 Dnote Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package view + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" +) + +func TestGetNewlineIdx(t *testing.T) { + testCases := []struct { + input string + expected int + }{ + { + input: "hello\nworld", + expected: 5, + }, + { + input: "hello\r\nworld", + expected: 5, + }, + { + input: "no newline here", + expected: -1, + }, + { + input: "", + expected: -1, + }, + { + input: "\n", + expected: 0, + }, + { + input: "\r\n", + expected: 0, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("input: %q", tc.input), func(t *testing.T) { + got := getNewlineIdx(tc.input) + assert.Equal(t, got, tc.expected, "newline index mismatch") + }) + } +} + +func TestFormatBody(t *testing.T) { + testCases := []struct { + input string + expectedBody string + expectedExcerpt bool + }{ + { + input: "single line", + expectedBody: "single line", + expectedExcerpt: false, + }, + { + input: "first line\nsecond line", + expectedBody: "first line", + expectedExcerpt: true, + }, + { + input: "first line\r\nsecond line", + expectedBody: "first line", + expectedExcerpt: true, + }, + { + input: " spaced line ", + expectedBody: "spaced line", + expectedExcerpt: false, + }, + { + input: " first line \nsecond line", + expectedBody: "first line", + expectedExcerpt: true, + }, + { + input: "", + expectedBody: "", + expectedExcerpt: false, + }, + { + input: "line with trailing newline\n", + expectedBody: "line with trailing newline", + expectedExcerpt: false, + }, + { + input: "line with trailing newlines\n\n", + expectedBody: "line with trailing newlines", + expectedExcerpt: false, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("input: %q", tc.input), func(t *testing.T) { + gotBody, gotExcerpt := formatBody(tc.input) + assert.Equal(t, gotBody, tc.expectedBody, "formatted body mismatch") + assert.Equal(t, gotExcerpt, tc.expectedExcerpt, "excerpt flag mismatch") + }) + } +} + +func TestListNotes(t *testing.T) { + // Setup + db := database.InitTestMemoryDB(t) + defer db.Close() + + bookUUID := "js-book-uuid" + database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", bookUUID, "javascript") + database.MustExec(t, "inserting note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-1", bookUUID, "first note", 1515199943) + database.MustExec(t, "inserting note 2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-2", bookUUID, "multiline note\nwith second line", 1515199945) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + // Execute + err := listNotes(ctx, &buf, "javascript") + if err != nil { + t.Fatal(err) + } + + got := buf.String() + + // Verify output + assert.Equal(t, strings.Contains(got, "on book javascript"), true, "should show book name") + assert.Equal(t, strings.Contains(got, "first note"), true, "should contain first note") + assert.Equal(t, strings.Contains(got, "multiline note"), true, "should show first line of multiline note") + assert.Equal(t, strings.Contains(got, "[---More---]"), true, "should show more indicator for multiline note") + assert.Equal(t, strings.Contains(got, "with second line"), false, "should not show second line of multiline note") +} + +func TestListBooks(t *testing.T) { + // Setup + db := database.InitTestMemoryDB(t) + defer db.Close() + + b1UUID := "js-book-uuid" + b2UUID := "linux-book-uuid" + + database.MustExec(t, "inserting book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "javascript") + database.MustExec(t, "inserting book 2", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b2UUID, "linux") + + // Add notes to test count + database.MustExec(t, "inserting note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-1", b1UUID, "note body 1", 1515199943) + database.MustExec(t, "inserting note 2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-2", b1UUID, "note body 2", 1515199944) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + // Execute + err := listBooks(ctx, &buf, false) + if err != nil { + t.Fatal(err) + } + + got := buf.String() + + // Verify output + assert.Equal(t, strings.Contains(got, "javascript"), true, "should contain javascript book") + assert.Equal(t, strings.Contains(got, "linux"), true, "should contain linux book") + assert.Equal(t, strings.Contains(got, "(2)"), true, "should show 2 notes for javascript") +} diff --git a/pkg/cli/cmd/view/note.go b/pkg/cli/cmd/view/note.go new file mode 100644 index 00000000..f853dd9a --- /dev/null +++ b/pkg/cli/cmd/view/note.go @@ -0,0 +1,47 @@ +/* Copyright 2025 Dnote Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package view + +import ( + "io" + "strconv" + + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" + "github.com/dnote/dnote/pkg/cli/output" + "github.com/pkg/errors" +) + +func viewNote(ctx context.DnoteCtx, w io.Writer, noteRowIDArg string, contentOnly bool) error { + noteRowID, err := strconv.Atoi(noteRowIDArg) + if err != nil { + return errors.Wrap(err, "invalid rowid") + } + + db := ctx.DB + info, err := database.GetNoteInfo(db, noteRowID) + if err != nil { + return err + } + + if contentOnly { + output.NoteContent(w, info) + } else { + output.NoteInfo(w, info) + } + + return nil +} diff --git a/pkg/cli/cmd/view/note_test.go b/pkg/cli/cmd/view/note_test.go new file mode 100644 index 00000000..36e9aa84 --- /dev/null +++ b/pkg/cli/cmd/view/note_test.go @@ -0,0 +1,90 @@ +/* Copyright 2025 Dnote Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package view + +import ( + "bytes" + "strings" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" +) + +func TestViewNote(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + bookUUID := "test-book-uuid" + database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", bookUUID, "golang") + database.MustExec(t, "inserting note", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", + "note-uuid", bookUUID, "test note content", 1515199943000000000) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "1", false) + if err != nil { + t.Fatal(err) + } + + got := buf.String() + assert.Equal(t, strings.Contains(got, "test note content"), true, "should contain note content") +} + +func TestViewNoteContentOnly(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + bookUUID := "test-book-uuid" + database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", bookUUID, "golang") + database.MustExec(t, "inserting note", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", + "note-uuid", bookUUID, "test note content", 1515199943000000000) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "1", true) + if err != nil { + t.Fatal(err) + } + + got := buf.String() + assert.Equal(t, got, "test note content", "should contain only note content") +} + +func TestViewNoteInvalidRowID(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "not-a-number", false) + assert.NotEqual(t, err, nil, "should return error for invalid rowid") +} + +func TestViewNoteNotFound(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "999", false) + assert.NotEqual(t, err, nil, "should return error for non-existent note") +} diff --git a/pkg/cli/cmd/view/view.go b/pkg/cli/cmd/view/view.go index 62f50a53..57b17dbd 100644 --- a/pkg/cli/cmd/view/view.go +++ b/pkg/cli/cmd/view/view.go @@ -16,14 +16,13 @@ package view import ( + "os" + "github.com/dnote/dnote/pkg/cli/context" "github.com/dnote/dnote/pkg/cli/infra" + "github.com/dnote/dnote/pkg/cli/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - - "github.com/dnote/dnote/pkg/cli/cmd/cat" - "github.com/dnote/dnote/pkg/cli/cmd/ls" - "github.com/dnote/dnote/pkg/cli/utils" ) var example = ` @@ -68,27 +67,26 @@ func NewCmd(ctx context.DnoteCtx) *cobra.Command { func newRun(ctx context.DnoteCtx) infra.RunEFunc { return func(cmd *cobra.Command, args []string) error { - var run infra.RunEFunc - if len(args) == 0 { - run = ls.NewRun(ctx, nameOnly) + // List all books + return listBooks(ctx, os.Stdout, nameOnly) } else if len(args) == 1 { if nameOnly { return errors.New("--name-only flag is only valid when viewing books") } if utils.IsNumber(args[0]) { - run = cat.NewRun(ctx, contentOnly) + // View a note by index + return viewNote(ctx, os.Stdout, args[0], contentOnly) } else { - run = ls.NewRun(ctx, false) + // List notes in a book + return listNotes(ctx, os.Stdout, args[0]) } } else if len(args) == 2 { - // DEPRECATED: passing book name to view command is deprecated - run = cat.NewRun(ctx, false) - } else { - return errors.New("Incorrect number of arguments") + // View a note in a book (book name + note index) + return viewNote(ctx, os.Stdout, args[1], contentOnly) } - return run(cmd, args) + return errors.New("Incorrect number of arguments") } } diff --git a/pkg/cli/main.go b/pkg/cli/main.go index 2afbaf36..2fb1c564 100644 --- a/pkg/cli/main.go +++ b/pkg/cli/main.go @@ -26,12 +26,10 @@ import ( // commands "github.com/dnote/dnote/pkg/cli/cmd/add" - "github.com/dnote/dnote/pkg/cli/cmd/cat" "github.com/dnote/dnote/pkg/cli/cmd/edit" "github.com/dnote/dnote/pkg/cli/cmd/find" "github.com/dnote/dnote/pkg/cli/cmd/login" "github.com/dnote/dnote/pkg/cli/cmd/logout" - "github.com/dnote/dnote/pkg/cli/cmd/ls" "github.com/dnote/dnote/pkg/cli/cmd/remove" "github.com/dnote/dnote/pkg/cli/cmd/root" "github.com/dnote/dnote/pkg/cli/cmd/sync" @@ -79,10 +77,8 @@ func main() { root.Register(login.NewCmd(*ctx)) root.Register(logout.NewCmd(*ctx)) root.Register(add.NewCmd(*ctx)) - root.Register(ls.NewCmd(*ctx)) root.Register(sync.NewCmd(*ctx)) root.Register(version.NewCmd(*ctx)) - root.Register(cat.NewCmd(*ctx)) root.Register(view.NewCmd(*ctx)) root.Register(find.NewCmd(*ctx)) diff --git a/pkg/cli/main_test.go b/pkg/cli/main_test.go index 5da1915e..727a5c3e 100644 --- a/pkg/cli/main_test.go +++ b/pkg/cli/main_test.go @@ -20,6 +20,7 @@ import ( "log" "os" "os/exec" + "strings" "testing" "github.com/dnote/dnote/pkg/assert" @@ -568,3 +569,65 @@ func TestDBPathFlag(t *testing.T) { db2.QueryRow("SELECT count(*) FROM books WHERE label = ?", "db1-book").Scan(&db2HasDB1Book) assert.Equal(t, db2HasDB1Book, 0, "db2 should not have db1's book") } + +func TestView(t *testing.T) { + t.Run("view note by rowid", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup4(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "1") + + assert.Equal(t, strings.Contains(output, "Booleans have toString()"), true, "should contain note content") + assert.Equal(t, strings.Contains(output, "book name"), true, "should show metadata") + }) + + t.Run("view note content only", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup4(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "1", "--content-only") + + assert.Equal(t, strings.Contains(output, "Booleans have toString()"), true, "should contain note content") + assert.Equal(t, strings.Contains(output, "book name"), false, "should not show metadata") + }) + + t.Run("list books", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup1(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view") + + assert.Equal(t, strings.Contains(output, "js"), true, "should list js book") + assert.Equal(t, strings.Contains(output, "linux"), true, "should list linux book") + }) + + t.Run("list notes in book", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup2(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "js") + + assert.Equal(t, strings.Contains(output, "n1 body"), true, "should list note 1") + assert.Equal(t, strings.Contains(output, "n2 body"), true, "should list note 2") + }) + + t.Run("view note by book name and rowid", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup4(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "js", "2") + + assert.Equal(t, strings.Contains(output, "Date object implements mathematical comparisons"), true, "should contain note content") + assert.Equal(t, strings.Contains(output, "book name"), true, "should show metadata") + }) +} diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go index fe6e3c87..d272ba88 100644 --- a/pkg/cli/output/output.go +++ b/pkg/cli/output/output.go @@ -19,6 +19,7 @@ package output import ( "fmt" + "io" "time" "github.com/dnote/dnote/pkg/cli/database" @@ -26,7 +27,7 @@ import ( ) // NoteInfo prints a note information -func NoteInfo(info database.NoteInfo) { +func NoteInfo(w io.Writer, info database.NoteInfo) { log.Infof("book name: %s\n", info.BookLabel) log.Infof("created at: %s\n", time.Unix(0, info.AddedOn).Format("Jan 2, 2006 3:04pm (MST)")) if info.EditedOn != 0 { @@ -35,13 +36,13 @@ func NoteInfo(info database.NoteInfo) { log.Infof("note id: %d\n", info.RowID) log.Infof("note uuid: %s\n", info.UUID) - fmt.Printf("\n------------------------content------------------------\n") - fmt.Printf("%s", info.Content) - fmt.Printf("\n-------------------------------------------------------\n") + fmt.Fprintf(w, "\n------------------------content------------------------\n") + fmt.Fprintf(w, "%s", info.Content) + fmt.Fprintf(w, "\n-------------------------------------------------------\n") } -func NoteContent(info database.NoteInfo) { - fmt.Printf("%s", info.Content) +func NoteContent(w io.Writer, info database.NoteInfo) { + fmt.Fprintf(w, "%s", info.Content) } // BookInfo prints a note information diff --git a/pkg/cli/testutils/main.go b/pkg/cli/testutils/main.go index 581cbd7f..db3282d7 100644 --- a/pkg/cli/testutils/main.go +++ b/pkg/cli/testutils/main.go @@ -144,7 +144,7 @@ type RunDnoteCmdOptions struct { } // RunDnoteCmd runs a dnote command -func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg ...string) { +func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg ...string) string { t.Logf("running: %s %s", binaryName, strings.Join(arg, " ")) cmd, stderr, stdout, err := NewDnoteCmd(opts, binaryName, arg...) @@ -162,6 +162,8 @@ func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg . // Print stdout if and only if test fails later t.Logf("\n%s", stdout) + + return stdout.String() } // WaitDnoteCmd runs a dnote command and passes stdout to the callback. From 9fa312e3fc6139788533ca6cd1ada8c16a10519c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:48:59 -0800 Subject: [PATCH 4/5] Bump golang.org/x/crypto from 0.42.0 to 0.45.0 (#716) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.42.0 to 0.45.0. - [Commits](https://github.com/golang/crypto/compare/v0.42.0...v0.45.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.45.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 ++++----- go.sum | 18 ++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 3b87c5ec..48dfc85c 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,9 @@ require ( github.com/mattn/go-sqlite3 v1.14.32 github.com/pkg/errors v0.9.1 github.com/radovskyb/watcher v1.0.7 - github.com/robfig/cron v1.2.0 github.com/sergi/go-diff v1.3.1 github.com/spf13/cobra v1.10.1 - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.45.0 golang.org/x/time v0.13.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v2 v2.4.0 @@ -36,9 +35,9 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/testify v1.8.1 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index e175da0d..8b3c653a 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -73,15 +71,15 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From f34a96abbe47e8b516ea7cac2bdec06c64c01493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:11:45 -0800 Subject: [PATCH 5/5] Bump immutable from 5.1.3 to 5.1.5 in /pkg/server/assets (#718) Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.3 to 5.1.5. - [Release notes](https://github.com/immutable-js/immutable-js/releases) - [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.3...v5.1.5) --- updated-dependencies: - dependency-name: immutable dependency-version: 5.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkg/server/assets/package-lock.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/server/assets/package-lock.json b/pkg/server/assets/package-lock.json index 610d8eb4..7eb1c411 100644 --- a/pkg/server/assets/package-lock.json +++ b/pkg/server/assets/package-lock.json @@ -363,10 +363,11 @@ } }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", - "dev": true + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" }, "node_modules/is-extglob": { "version": "2.1.1",