mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Add support for npm unpublish (#20688)
				
					
				
			This commit is contained in:
		| @@ -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/test_package@1.0.0 | ||||
| ``` | ||||
|  | ||||
| ## Install a package | ||||
|  | ||||
| To install a package from the package registry, execute the following command: | ||||
| @@ -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 | ||||
| ``` | ||||
|   | ||||
| @@ -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 + `" | ||||
| 			}, | ||||
| 			"dist": { | ||||
| 			  "integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", | ||||
| 			  "shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90" | ||||
| 			"versions": { | ||||
| 			  "` + version + `": { | ||||
| 				"name": "` + packageName + `", | ||||
| 				"version": "` + version + `", | ||||
| 				"description": "` + packageDescription + `", | ||||
| 				"author": { | ||||
| 				  "name": "` + packageAuthor + `" | ||||
| 				}, | ||||
| 				"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)) | ||||
| @@ -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) | ||||
|  | ||||
| @@ -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) | ||||
| 	}) | ||||
| @@ -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) | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user