Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Pub packages #20560

Merged
merged 4 commits into from
Aug 7, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions docs/content/doc/packages/overview.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The following package managers are currently supported:
| [Maven]({{< relref "doc/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` |
| [npm]({{< relref "doc/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn` |
| [NuGet]({{< relref "doc/packages/nuget.en-us.md" >}}) | .NET | `nuget` |
| [Pub]({{< relref "doc/packages/pub.en-us.md" >}}) | Dart | `dart`, `flutter` |
| [PyPI]({{< relref "doc/packages/pypi.en-us.md" >}}) | Python | `pip`, `twine` |
| [RubyGems]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Ruby | `gem`, `Bundler` |

Expand Down
83 changes: 83 additions & 0 deletions docs/content/doc/packages/pub.en-us.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
date: "2022-07-31T00:00:00+00:00"
title: "Pub Packages Repository"
slug: "packages/pub"
draft: false
toc: false
menu:
sidebar:
parent: "packages"
name: "Pub"
weight: 90
identifier: "pub"
---

# Pub Packages Repository

Publish [Pub](https://dart.dev/guides/packages) packages for your user or organization.

**Table of Contents**

{{< toc >}}

## Requirements

To work with the Pub package registry, you need to use the tools [dart](https://dart.dev/tools/dart-tool) and/or [flutter](https://docs.flutter.dev/reference/flutter-cli).

The following examples use dart.

## Configuring the package registry

To register the package registry and provide credentials, execute:

```shell
dart pub token add https://gitea.example.com/api/packages/{owner}/pub
```

| Placeholder | Description |
| ------------ | ----------- |
| `owner` | The owner of the package. |

You need to provide your [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}).

## Publish a package

To publish a package, edit the `pubspec.yaml` and add the following line:

```yaml
publish_to: https://gitea.example.com/api/packages/{owner}/pub
```

| Placeholder | Description |
| ------------ | ----------- |
| `owner` | The owner of the package. |

Now you can publish the package by running the following command:

```shell
dart pub publish
```

You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first.

## Install a package

To install a Pub package from the package registry, execute the following command:

```shell
dart pub add {package_name} --hosted-url=https://gitea.example.com/api/packages/{owner}/pub/
```

| Parameter | Description |
| ----------------- | ----------- |
| `owner` | The owner of the package. |
| `package_name` | The package name. |

For example:

```shell
# use latest version
dart pub add mypackage --hosted-url=https://gitea.example.com/api/packages/testuser/pub/
# specify version
dart pub add mypackage:1.0.8 --hosted-url=https://gitea.example.com/api/packages/testuser/pub/
```
2 changes: 1 addition & 1 deletion docs/content/doc/packages/pypi.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "PyPI"
weight: 90
weight: 100
identifier: "pypi"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/doc/packages/rubygems.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "RubyGems"
weight: 100
weight: 110
identifier: "rubygems"
---

Expand Down
179 changes: 179 additions & 0 deletions integrations/api_packages_pub_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
pub_module "code.gitea.io/gitea/modules/packages/pub"

"github.com/stretchr/testify/assert"
)

func TestPackagePub(t *testing.T) {
defer prepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)

token := "Bearer " + getUserToken(t, user.Name)

packageName := "test_package"
packageVersion := "1.0.1"
packageDescription := "Test Description"

filename := fmt.Sprintf("%s.tar.gz", packageVersion)

pubspecContent := `name: ` + packageName + `
version: ` + packageVersion + `
description: ` + packageDescription

var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
archive := tar.NewWriter(zw)
archive.WriteHeader(&tar.Header{
Name: "pubspec.yaml",
Mode: 0o600,
Size: int64(len(pubspecContent)),
})
archive.Write([]byte(pubspecContent))
archive.Close()
zw.Close()
content := buf.Bytes()

root := fmt.Sprintf("/api/packages/%s/pub", user.Name)

t.Run("Upload", func(t *testing.T) {
defer PrintCurrentTest(t)()

uploadURL := root + "/api/packages/versions/new"

req := NewRequest(t, "GET", uploadURL)
MakeRequest(t, req, http.StatusUnauthorized)

req = NewRequest(t, "GET", uploadURL)
addTokenAuthHeader(req, token)
resp := MakeRequest(t, req, http.StatusOK)

type UploadRequest struct {
URL string `json:"url"`
Fields map[string]string `json:"fields"`
}

var result UploadRequest
DecodeJSON(t, resp, &result)

assert.Empty(t, result.Fields)

uploadFile := func(t *testing.T, url string, content []byte, expectedStatus int) *httptest.ResponseRecorder {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", "dummy.tar.gz")
_, _ = io.Copy(part, bytes.NewReader(content))

_ = writer.Close()

req := NewRequestWithBody(t, "POST", url, body)
req.Header.Add("Content-Type", writer.FormDataContentType())
addTokenAuthHeader(req, token)
return MakeRequest(t, req, expectedStatus)
}

resp = uploadFile(t, result.URL, content, http.StatusNoContent)

req = NewRequest(t, "GET", resp.Header().Get("Location"))
addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusOK)

pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypePub)
assert.NoError(t, err)
assert.Len(t, pvs, 1)

pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
assert.NoError(t, err)
assert.NotNil(t, pd.SemVer)
assert.IsType(t, &pub_module.Metadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)

pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
assert.NoError(t, err)
assert.Len(t, pfs, 1)
assert.Equal(t, filename, pfs[0].Name)
assert.True(t, pfs[0].IsLead)

pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
assert.NoError(t, err)
assert.Equal(t, int64(len(content)), pb.Size)

resp = uploadFile(t, result.URL, content, http.StatusBadRequest)
})

t.Run("Download", func(t *testing.T) {
defer PrintCurrentTest(t)()

req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s/%s", root, packageName, packageVersion))
resp := MakeRequest(t, req, http.StatusOK)

type VersionMetadata struct {
Version string `json:"version"`
ArchiveURL string `json:"archive_url"`
Published time.Time `json:"published"`
Pubspec interface{} `json:"pubspec,omitempty"`
}

var result VersionMetadata
DecodeJSON(t, resp, &result)

assert.Equal(t, packageVersion, result.Version)
assert.NotNil(t, result.Pubspec)

req = NewRequest(t, "GET", result.ArchiveURL)
resp = MakeRequest(t, req, http.StatusOK)

assert.Equal(t, content, resp.Body.Bytes())
})

t.Run("EnumeratePackageVersions", func(t *testing.T) {
defer PrintCurrentTest(t)()

req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s", root, packageName))
resp := MakeRequest(t, req, http.StatusOK)

type VersionMetadata struct {
Version string `json:"version"`
ArchiveURL string `json:"archive_url"`
Published time.Time `json:"published"`
Pubspec interface{} `json:"pubspec,omitempty"`
}

type PackageVersions struct {
Name string `json:"name"`
Latest *VersionMetadata `json:"latest"`
Versions []*VersionMetadata `json:"versions"`
}

var result PackageVersions
DecodeJSON(t, resp, &result)

assert.Equal(t, packageName, result.Name)
assert.NotNil(t, result.Latest)
assert.Len(t, result.Versions, 1)
assert.Equal(t, result.Latest.Version, result.Versions[0].Version)
assert.Equal(t, packageVersion, result.Latest.Version)
assert.NotNil(t, result.Latest.Pubspec)
})
}
3 changes: 3 additions & 0 deletions models/packages/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/modules/packages/npm"
"code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/packages/pub"
"code.gitea.io/gitea/modules/packages/pypi"
"code.gitea.io/gitea/modules/packages/rubygems"

Expand Down Expand Up @@ -143,6 +144,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
metadata = &npm.Metadata{}
case TypeMaven:
metadata = &maven.Metadata{}
case TypePub:
metadata = &pub.Metadata{}
case TypePyPI:
metadata = &pypi.Metadata{}
case TypeRubyGems:
Expand Down
5 changes: 5 additions & 0 deletions models/packages/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
TypeMaven Type = "maven"
TypeNpm Type = "npm"
TypeNuGet Type = "nuget"
TypePub Type = "pub"
TypePyPI Type = "pypi"
TypeRubyGems Type = "rubygems"
)
Expand All @@ -62,6 +63,8 @@ func (pt Type) Name() string {
return "npm"
case TypeNuGet:
return "NuGet"
case TypePub:
return "Pub"
case TypePyPI:
return "PyPI"
case TypeRubyGems:
Expand Down Expand Up @@ -89,6 +92,8 @@ func (pt Type) SVGName() string {
return "gitea-npm"
case TypeNuGet:
return "gitea-nuget"
case TypePub:
return "gitea-pub"
case TypePyPI:
return "gitea-python"
case TypeRubyGems:
Expand Down
Loading