Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,14 @@ Per-category updates are partial — only categories you name are changed; other

### Extension Management

- `kernel extensions list` - List all uploaded extensions
- `kernel extensions list` - List all uploaded extensions, including available checksums
- `--output json`, `-o json` - Output raw JSON array
- `kernel extensions get <id-or-name>` - Show extension metadata (id, name, created, size, last used)
- `kernel extensions get <id-or-name>` - Show extension metadata (id, name, checksum, created, size, last used)
- `--output json`, `-o json` - Output raw JSON object
- `kernel extensions upload <directory>` - Upload an unpacked browser extension directory
- `--name <name>` - Optional unique extension name
- `--output json`, `-o json` - Output raw JSON object
- Successful uploads show the stored archive checksum
- `kernel extensions download <id-or-name>` - Download an extension archive
- `--to <directory>` - Output directory (required)
- `kernel extensions download-web-store <url>` - Download an extension from the Chrome Web Store
Expand Down
23 changes: 7 additions & 16 deletions cmd/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,12 @@ func (e ExtensionsCmd) List(ctx context.Context, in ExtensionsListInput) error {
pterm.Info.Println("No extensions found")
return nil
}
rows := pterm.TableData{{"Extension ID", "Name", "Created At", "Size (bytes)", "Last Used At"}}
rows := pterm.TableData{{"Extension ID", "Name", "Checksum", "Created At", "Size (bytes)", "Last Used At"}}
for _, it := range items {
name := it.Name
if name == "" {
name = "-"
}
rows = append(rows, []string{
it.ID,
name,
util.FirstOrDash(it.Name),
util.FirstOrDash(it.Checksum),
util.FormatLocal(it.CreatedAt),
fmt.Sprintf("%d", it.SizeBytes),
util.FormatLocal(it.LastUsedAt),
Expand Down Expand Up @@ -165,17 +162,14 @@ func (e ExtensionsCmd) Get(ctx context.Context, in ExtensionsGetInput) error {
return util.PrintPrettyJSON(item)
}

name := item.Name
if name == "" {
name = "-"
}
rows := pterm.TableData{{"Property", "Value"}}
rows = append(rows, []string{"ID", item.ID})
rows = append(rows, []string{"Name", name})
rows = append(rows, []string{"Name", util.FirstOrDash(item.Name)})
rows = append(rows, []string{"Created At", util.FormatLocal(item.CreatedAt)})
rows = append(rows, []string{"Size (bytes)", fmt.Sprintf("%d", item.SizeBytes)})
rows = append(rows, []string{"Last Used At", util.FormatLocal(item.LastUsedAt)})
PrintTableNoPad(rows, true)
pterm.Printf("Checksum: %s\n", util.FirstOrDash(item.Checksum))
return nil
}

Expand Down Expand Up @@ -432,16 +426,13 @@ func (e ExtensionsCmd) Upload(ctx context.Context, in ExtensionsUploadInput) err
return util.PrintPrettyJSON(item)
}

name := item.Name
if name == "" {
name = "-"
}
rows := pterm.TableData{{"Property", "Value"}}
rows = append(rows, []string{"ID", item.ID})
rows = append(rows, []string{"Name", name})
rows = append(rows, []string{"Name", util.FirstOrDash(item.Name)})
rows = append(rows, []string{"Created At", util.FormatLocal(item.CreatedAt)})
rows = append(rows, []string{"Size (bytes)", fmt.Sprintf("%d", item.SizeBytes)})
PrintTableNoPad(rows, true)
pterm.Printf("Checksum: %s\n", util.FirstOrDash(item.Checksum))
return nil
}

Expand Down
37 changes: 33 additions & 4 deletions cmd/extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"os"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/kernel/kernel-go-sdk"
"github.com/kernel/kernel-go-sdk/option"
"github.com/kernel/kernel-go-sdk/packages/pagination"
"github.com/pterm/pterm"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -67,10 +69,11 @@ func (f *FakeExtensionsService) Upload(ctx context.Context, body kernel.Extensio

func TestExtensionsGet_Table(t *testing.T) {
buf := capturePtermOutput(t)
const checksum = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
fake := &FakeExtensionsService{
GetFunc: func(ctx context.Context, idOrName string, opts ...option.RequestOption) (*kernel.ExtensionGetResponse, error) {
assert.Equal(t, "my-ext", idOrName)
return &kernel.ExtensionGetResponse{ID: "e-123", Name: "my-ext", CreatedAt: time.Unix(0, 0), SizeBytes: 42}, nil
return &kernel.ExtensionGetResponse{ID: "e-123", Name: "my-ext", Checksum: checksum, CreatedAt: time.Unix(0, 0), SizeBytes: 42}, nil
},
}
e := ExtensionsCmd{extensions: fake}
Expand All @@ -79,9 +82,30 @@ func TestExtensionsGet_Table(t *testing.T) {
out := buf.String()
assert.Contains(t, out, "e-123")
assert.Contains(t, out, "my-ext")
assert.Contains(t, out, "Checksum: "+checksum)
assert.Contains(t, out, "42")
}

func TestExtensionsGet_JSONOutput(t *testing.T) {
ptermOutput := capturePtermOutput(t)
fake := &FakeExtensionsService{
GetFunc: func(ctx context.Context, idOrName string, opts ...option.RequestOption) (*kernel.ExtensionGetResponse, error) {
var response kernel.ExtensionGetResponse
err := json.Unmarshal([]byte(`{"id":"e-123","name":"my-ext","checksum":"abc123","created_at":"1970-01-01T00:00:00Z","size_bytes":42}`), &response)
assert.NoError(t, err)
return &response, nil
},
}
e := ExtensionsCmd{extensions: fake}
var commandErr error
stdout := captureStdout(t, func() {
commandErr = e.Get(context.Background(), ExtensionsGetInput{Identifier: "my-ext", Output: "json"})
})
assert.NoError(t, commandErr)
assert.JSONEq(t, `{"id":"e-123","name":"my-ext","checksum":"abc123","created_at":"1970-01-01T00:00:00Z","size_bytes":42}`, stdout)
assert.Empty(t, ptermOutput.String())
}

func TestExtensionsList_Empty(t *testing.T) {
buf := capturePtermOutput(t)
fake := &FakeExtensionsService{}
Expand All @@ -93,16 +117,19 @@ func TestExtensionsList_Empty(t *testing.T) {
func TestExtensionsList_WithRows(t *testing.T) {
buf := capturePtermOutput(t)
created := time.Unix(0, 0)
rows := []kernel.ExtensionListResponse{{ID: "e1", Name: "alpha", CreatedAt: created, SizeBytes: 10}, {ID: "e2", Name: "", CreatedAt: created, SizeBytes: 20}}
rows := []kernel.ExtensionListResponse{{ID: "e1", Name: "alpha", Checksum: "abc123", CreatedAt: created, SizeBytes: 10}, {ID: "e2", Name: "legacy", CreatedAt: created, SizeBytes: 20}}
fake := &FakeExtensionsService{ListFunc: func(ctx context.Context, query kernel.ExtensionListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.ExtensionListResponse], error) {
return &pagination.OffsetPagination[kernel.ExtensionListResponse]{Items: rows}, nil
}}
e := ExtensionsCmd{extensions: fake}
_ = e.List(context.Background(), ExtensionsListInput{})
out := buf.String()
assert.Contains(t, out, "Checksum")
assert.Contains(t, out, "e1")
assert.Contains(t, out, "alpha")
assert.Contains(t, out, "abc123")
assert.Contains(t, out, "e2")
assert.Regexp(t, `legacy\s+\|\s+-`, pterm.RemoveColorFromString(out))
}

func TestExtensionsList_ForwardsLimitOffset(t *testing.T) {
Expand Down Expand Up @@ -205,21 +232,23 @@ func TestExtensionsDownloadWebStore_InvalidOS(t *testing.T) {

func TestExtensionsUpload_Success(t *testing.T) {
buf := capturePtermOutput(t)
const checksum = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
dir := t.TempDir()
// create a sample file inside dir
err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte("{}"), 0644)
assert.NoError(t, err)

fake := &FakeExtensionsService{UploadFunc: func(ctx context.Context, body kernel.ExtensionUploadParams, opts ...option.RequestOption) (*kernel.ExtensionUploadResponse, error) {
return &kernel.ExtensionUploadResponse{ID: "e1", Name: "myext", CreatedAt: time.Unix(0, 0), SizeBytes: 10}, nil
return &kernel.ExtensionUploadResponse{ID: "e1", Name: "myext", Checksum: checksum, CreatedAt: time.Unix(0, 0), SizeBytes: 10}, nil
}}
e := ExtensionsCmd{extensions: fake}
_ = e.Upload(context.Background(), ExtensionsUploadInput{Dir: dir, Name: "myext"})
assert.NoError(t, e.Upload(context.Background(), ExtensionsUploadInput{Dir: dir, Name: "myext"}))
out := buf.String()
assert.Contains(t, out, "ID")
assert.Contains(t, out, "e1")
assert.Contains(t, out, "Name")
assert.Contains(t, out, "myext")
assert.Contains(t, out, "Checksum: "+checksum)
}

func TestExtensionsUpload_InvalidDir(t *testing.T) {
Expand Down
Loading