mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-04 22:30:01 +00:00
enhance(packages/npm): expand version metadata and support npm deprecate (#37890)
Fixes #21624 Adds the npm package version metadata fields that Gitea's npm registry was previously dropping on publish, and implements the `npm deprecate` command, which Gitea did not accept before. ### New / pass-through metadata fields The following are now parsed from the publish payload, persisted in the stored `npm.Metadata`, and re-emitted on the abbreviated version manifest returned to npm clients: - `hasInstallScript` — auto-detected from `scripts.preinstall` / `scripts.install` / `scripts.postinstall` (also honors a client-supplied value). Without this flag, `npm install` skips lifecycle scripts. - `_hasShrinkwrap` — authoritatively derived by inspecting the uploaded tarball for a top-level `*/npm-shrinkwrap.json` entry. Client-supplied values are ignored. Decompression failures fall back to `false` and do not block publish (integrity has already been validated). - `engines` (`map[string]string`) - `cpu`, `os` (`[]string`) - `directories` (`map[string]string`) - `funding` (`any`; preserves the spec's string / object / array shape) - `acceptDependencies` (`map[string]string`) - `deprecated` (`string`) `peerDependenciesMeta` was already in the stored struct but is now exercised by tests. ### `npm deprecate` support `npm deprecate <pkg-spec> <message>` PUTs the package document to the same URL as publish but with no `_attachments`. The router now detects that shape and routes to a new handler that updates each affected version's stored `Metadata.Deprecated` via `packages_model.UpdateVersion`. An empty message clears the flag (undeprecate). Unknown versions are silently skipped, matching npm's behavior. No new routes were added. Supported invocations include: - `npm deprecate my-thing@"< 0.2.3" "critical bug fixed in v0.2.3"` - `npm deprecate my-thing@1.x "1.x is no longer supported"` - `npm deprecate my-thing@1.0.0 ""` (undeprecate) --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
committed by
GitHub
parent
c2ebe3724f
commit
6487d14855
@@ -71,6 +71,15 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
|
||||
OptionalDependencies: metadata.OptionalDependencies,
|
||||
Readme: metadata.Readme,
|
||||
Bin: metadata.Bin,
|
||||
HasInstallScript: metadata.HasInstallScript,
|
||||
HasShrinkwrap: metadata.HasShrinkwrap,
|
||||
Engines: metadata.Engines,
|
||||
CPU: metadata.CPU,
|
||||
OS: metadata.OS,
|
||||
Directories: metadata.Directories,
|
||||
Funding: metadata.Funding,
|
||||
AcceptDependencies: metadata.AcceptDependencies,
|
||||
Deprecated: metadata.Deprecated,
|
||||
Dist: npm_module.PackageDistribution{
|
||||
Shasum: pd.Files[0].Blob.HashSHA1,
|
||||
Integrity: "sha512-" + base64.StdEncoding.EncodeToString(hashBytes),
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/optional"
|
||||
packages_module "gitea.dev/modules/packages"
|
||||
npm_module "gitea.dev/modules/packages/npm"
|
||||
@@ -154,7 +155,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
|
||||
|
||||
// UploadPackage creates a new package
|
||||
func UploadPackage(ctx *context.Context) {
|
||||
npmPackage, err := npm_module.ParsePackage(ctx.Req.Body)
|
||||
npmPackage, deprecation, err := npm_module.ParseUpload(ctx.Req.Body)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
@@ -164,6 +165,12 @@ func UploadPackage(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// `npm deprecate` reuses the publish endpoint with no `_attachments`.
|
||||
if deprecation != nil {
|
||||
deprecatePackage(ctx, deprecation)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByURLRelax(ctx, npmPackage.Metadata.Repository.URL)
|
||||
if err == nil {
|
||||
canWrite := repo.OwnerID == ctx.Doer.ID
|
||||
@@ -252,6 +259,57 @@ func DeletePreview(ctx *context.Context) {
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// deprecatePackage handles an `npm deprecate` request, which is a PUT to the
|
||||
// package URL with no attachments and a `deprecated` string set on each
|
||||
// affected version (empty string means undeprecate).
|
||||
func deprecatePackage(ctx *context.Context, dep *npm_module.PackageDeprecation) {
|
||||
if len(dep.Versions) == 0 {
|
||||
apiError(ctx, http.StatusBadRequest, "npm deprecate request contains no versions")
|
||||
return
|
||||
}
|
||||
|
||||
// Run per-version updates in one transaction so a partial failure does
|
||||
// not leave the package in a half-applied state.
|
||||
err := db.WithTx(ctx, func(txCtx std_ctx.Context) error {
|
||||
for version, message := range dep.Versions {
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(txCtx, ctx.Package.Owner.ID, packages_model.TypeNpm, dep.PackageName, version)
|
||||
if err != nil {
|
||||
if errors.Is(err, packages_model.ErrPackageNotExist) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
metadata := &npm_module.Metadata{}
|
||||
if err := json.Unmarshal([]byte(pv.MetadataJSON), metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if metadata.Deprecated == message {
|
||||
continue
|
||||
}
|
||||
metadata.Deprecated = message
|
||||
|
||||
raw, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pv.MetadataJSON = string(raw)
|
||||
|
||||
if err := packages_model.UpdateVersion(txCtx, pv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// DeletePackageVersion deletes the package version
|
||||
func DeletePackageVersion(ctx *context.Context) {
|
||||
packageName := packageNameFromParams(ctx)
|
||||
|
||||
Reference in New Issue
Block a user