Skip to content

Commit

Permalink
Add support for npm unpublish (go-gitea#20688)
Browse files Browse the repository at this point in the history
  • Loading branch information
KN4CK3R authored and Sysoev, Vladimir committed Aug 10, 2022
1 parent c5068e8 commit 381ba48
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 27 deletions.
21 changes: 21 additions & 0 deletions docs/content/doc/packages/npm.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ npm publish

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

## Unpublish a package

Delete a package by running the following command:

```shell
npm unpublish {package_name}[@{package_version}]
```

| Parameter | Description |
| ----------------- | ----------- |
| `package_name` | The package name. |
| `package_version` | The package version. |

For example:

```shell
npm unpublish @test/test_package
npm unpublish @test/[email protected]
```

## Install a package

To install a package from the package registry, execute the following command:
Expand Down Expand Up @@ -113,6 +133,7 @@ The tag name must not be a valid version. All tag names which are parsable as a
npm install
npm ci
npm publish
npm unpublish
npm dist-tag
npm view
```
106 changes: 81 additions & 25 deletions integrations/api_packages_npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,33 +36,36 @@ func TestPackageNpm(t *testing.T) {
packageDescription := "Test Description"

data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
upload := `{
"_id": "` + packageName + `",
"name": "` + packageName + `",
"description": "` + packageDescription + `",
"dist-tags": {
"` + packageTag + `": "` + packageVersion + `"
},
"versions": {
"` + packageVersion + `": {

buildUpload := func(version string) string {
return `{
"_id": "` + packageName + `",
"name": "` + packageName + `",
"version": "` + packageVersion + `",
"description": "` + packageDescription + `",
"author": {
"name": "` + packageAuthor + `"
"dist-tags": {
"` + packageTag + `": "` + version + `"
},
"versions": {
"` + version + `": {
"name": "` + packageName + `",
"version": "` + version + `",
"description": "` + packageDescription + `",
"author": {
"name": "` + packageAuthor + `"
},
"dist": {
"integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==",
"shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90"
}
}
},
"dist": {
"integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==",
"shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90"
"_attachments": {
"` + packageName + `-` + version + `.tgz": {
"data": "` + data + `"
}
}
}
},
"_attachments": {
"` + packageName + `-` + packageVersion + `.tgz": {
"data": "` + data + `"
}
}
}`
}`
}

root := fmt.Sprintf("/api/packages/%s/npm/%s", user.Name, url.QueryEscape(packageName))
tagsRoot := fmt.Sprintf("/api/packages/%s/npm/-/package/%s/dist-tags", user.Name, url.QueryEscape(packageName))
Expand All @@ -71,7 +74,7 @@ func TestPackageNpm(t *testing.T) {
t.Run("Upload", func(t *testing.T) {
defer PrintCurrentTest(t)()

req := NewRequestWithBody(t, "PUT", root, strings.NewReader(upload))
req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion)))
req = addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusCreated)

Expand Down Expand Up @@ -103,7 +106,7 @@ func TestPackageNpm(t *testing.T) {
t.Run("UploadExists", func(t *testing.T) {
defer PrintCurrentTest(t)()

req := NewRequestWithBody(t, "PUT", root, strings.NewReader(upload))
req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion)))
req = addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusBadRequest)
})
Expand Down Expand Up @@ -219,4 +222,57 @@ func TestPackageNpm(t *testing.T) {
test(t, http.StatusOK, "dummy")
test(t, http.StatusOK, packageTag2)
})

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

req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion+"-dummy")))
req = addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusCreated)

req = NewRequest(t, "PUT", root+"/-rev/dummy")
MakeRequest(t, req, http.StatusUnauthorized)

req = NewRequest(t, "PUT", root+"/-rev/dummy")
req = addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusOK)

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

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

req := NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename))
MakeRequest(t, req, http.StatusUnauthorized)

req = NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename))
req = addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusOK)

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

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

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

req := NewRequest(t, "DELETE", root+"/-rev/dummy")
MakeRequest(t, req, http.StatusUnauthorized)

req = NewRequest(t, "DELETE", root+"/-rev/dummy")
req = addTokenAuthHeader(req, token)
MakeRequest(t, req, http.StatusOK)

pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
assert.NoError(t, err)
assert.Len(t, pvs, 0)
})
})
}
18 changes: 16 additions & 2 deletions routers/api/packages/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,26 @@ func Routes() *web.Route {
r.Group("/@{scope}/{id}", func() {
r.Get("", npm.PackageMetadata)
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
r.Group("/-/{version}/{filename}", func() {
r.Get("", npm.DownloadPackageFile)
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
})
r.Group("/-rev/{revision}", func() {
r.Delete("", npm.DeletePackage)
r.Put("", npm.DeletePreview)
}, reqPackageAccess(perm.AccessModeWrite))
})
r.Group("/{id}", func() {
r.Get("", npm.PackageMetadata)
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
r.Group("/-/{version}/{filename}", func() {
r.Get("", npm.DownloadPackageFile)
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
})
r.Group("/-rev/{revision}", func() {
r.Delete("", npm.DeletePackage)
r.Put("", npm.DeletePreview)
}, reqPackageAccess(perm.AccessModeWrite))
})
r.Group("/-/package/@{scope}/{id}/dist-tags", func() {
r.Get("", npm.ListPackageTags)
Expand Down
57 changes: 57 additions & 0 deletions routers/api/packages/npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,63 @@ func UploadPackage(ctx *context.Context) {
ctx.Status(http.StatusCreated)
}

// DeletePreview does nothing
// The client tells the server what package version it knows about after deleting a version.
func DeletePreview(ctx *context.Context) {
ctx.Status(http.StatusOK)
}

// DeletePackageVersion deletes the package version
func DeletePackageVersion(ctx *context.Context) {
packageName := packageNameFromParams(ctx)
packageVersion := ctx.Params("version")

err := packages_service.RemovePackageVersionByNameAndVersion(
ctx.Doer,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeNpm,
Name: packageName,
Version: packageVersion,
},
)
if err != nil {
if err == packages_model.ErrPackageNotExist {
apiError(ctx, http.StatusNotFound, err)
return
}
apiError(ctx, http.StatusInternalServerError, err)
return
}

ctx.Status(http.StatusOK)
}

// DeletePackage deletes the package and all versions
func DeletePackage(ctx *context.Context) {
packageName := packageNameFromParams(ctx)

pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}

if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, err)
return
}

for _, pv := range pvs {
if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
}

ctx.Status(http.StatusOK)
}

// ListPackageTags returns all tags for a package
func ListPackageTags(ctx *context.Context) {
packageName := packageNameFromParams(ctx)
Expand Down

0 comments on commit 381ba48

Please sign in to comment.