diff --git a/.changeset/rude-socks-walk.md b/.changeset/rude-socks-walk.md new file mode 100644 index 000000000..a6f41413d --- /dev/null +++ b/.changeset/rude-socks-walk.md @@ -0,0 +1,6 @@ +--- +'@verdaccio/types': patch +'@verdaccio/store': patch +--- + +feat: keep_readmes option when publishing packages diff --git a/packages/core/types/src/configuration.ts b/packages/core/types/src/configuration.ts index 676d55207..07ea4b2e5 100644 --- a/packages/core/types/src/configuration.ts +++ b/packages/core/types/src/configuration.ts @@ -202,8 +202,11 @@ export interface Security { api: APITokenOptions; } +export type ReadmeOptions = 'latest' | 'tagged' | 'all' | undefined; + export interface PublishOptions { allow_offline: boolean; + keep_readmes: ReadmeOptions; check_owners: boolean; } diff --git a/packages/store/src/lib/storage-utils.ts b/packages/store/src/lib/storage-utils.ts index 96d7b47de..20233afab 100644 --- a/packages/store/src/lib/storage-utils.ts +++ b/packages/store/src/lib/storage-utils.ts @@ -3,7 +3,14 @@ import semver from 'semver'; import { errorUtils, pkgUtils, searchUtils, validatioUtils } from '@verdaccio/core'; import { API_ERROR, DIST_TAGS, HTTP_STATUS, MAINTAINERS, USERS } from '@verdaccio/core'; -import { AttachMents, Manifest, Version, Versions } from '@verdaccio/types'; +import { + AttachMents, + GenericBody, + Manifest, + ReadmeOptions, + Version, + Versions, +} from '@verdaccio/types'; import { generateRandomHexString, isNil, isObject } from '@verdaccio/utils'; import { sortVersionsAndFilterInvalid } from './versions-utils'; @@ -92,10 +99,28 @@ export function getLatestReadme(pkg: Manifest): string { return readme; } -// FIXME: type any due this -export function cleanUpReadme(version: any): Version { +/** + * Cleanup readme from package version + * + * By default, we don't keep readmes for package versions, only one readme per package. + * Using publish.keep_readmes you can override this behavior and keep all readmes + * or only readmes for tagged versions. + */ +export function cleanUpReadme( + version: Version, + distTags?: GenericBody, + keepReadmes?: ReadmeOptions +): Version { + if (keepReadmes === 'all') { + return version; + } else if (keepReadmes === 'tagged') { + if (distTags && Object.values(distTags).includes(version.version)) { + return version; + } + } + if (isNil(version) === false) { - delete version.readme; + version.readme = ''; } return version; diff --git a/packages/store/src/storage.ts b/packages/store/src/storage.ts index d2b1f9405..94aca68c5 100644 --- a/packages/store/src/storage.ts +++ b/packages/store/src/storage.ts @@ -1404,11 +1404,11 @@ class Storage { ): Promise { debug(`add version %s package for %s`, version, name); await this.updatePackage(name, async (data: Manifest): Promise => { - // keep only one readme per package + // keep latest readme in manifest (on root level) data.readme = metadata.readme; debug('%s readme mutated', name); - // TODO: lodash remove - metadata = cleanUpReadme(metadata); + // removing other readmes depends on config + metadata = cleanUpReadme(metadata, metadata[DIST_TAGS], this.config?.publish?.keep_readmes); metadata.contributors = normalizeContributors(metadata.contributors as Author[]); debug('%s contributors normalized', name); @@ -1966,10 +1966,12 @@ class Storage { debug('new version from upstream %o', versionId); let version = remoteManifest.versions[versionId]; - // we don't keep readme for package versions, - // only one readme per package - // TODO: readme clean up could be saved in configured eventually - version = cleanUpReadme(version); + // removing readmes depends on config + version = cleanUpReadme( + version, + remoteManifest[DIST_TAGS], + this.config?.publish?.keep_readmes + ); debug('clean up readme for %o', versionId); version.contributors = normalizeContributors(version.contributors as Author[]); diff --git a/packages/store/test/storage-utils.spec.ts b/packages/store/test/storage-utils.spec.ts index 58f1d3e5e..db284c56a 100644 --- a/packages/store/test/storage-utils.spec.ts +++ b/packages/store/test/storage-utils.spec.ts @@ -1,11 +1,12 @@ import { describe, expect, test } from 'vitest'; import { DIST_TAGS } from '@verdaccio/core'; +import { generatePackageMetadata } from '@verdaccio/test-helper'; import { Manifest } from '@verdaccio/types'; -import { generatePackageMetadata } from '../../api/node_modules/@verdaccio/test-helper/build'; import { STORAGE, + cleanUpReadme, hasInvalidPublishBody, isDeprecatedManifest, isDifferentThanOne, @@ -356,4 +357,54 @@ describe('Storage Utils', () => { ).toBeTruthy(); }); }); + + describe('cleanUpReadme', () => { + describe('should keep only latest readme', () => { + test('should clean up readme (no dist-tags)', () => { + const manifest = generatePackageMetadata('foo'); + const version = manifest.versions['1.0.0']; + const cleanup = cleanUpReadme(version); + expect(cleanup.readme).toEqual(''); + }); + + test('should clean up readme (latest in dist-tag)', () => { + const manifest = generatePackageMetadata('foo'); + const version = manifest.versions['1.0.0']; + const cleanup = cleanUpReadme(version, manifest[DIST_TAGS]); + expect(cleanup.readme).toEqual(''); + }); + }); + + describe('should keep only tagged readme', () => { + test('should clean up readme (no dist-tags)', () => { + const manifest = generatePackageMetadata('foo'); + const version = manifest.versions['1.0.0']; + const cleanup = cleanUpReadme(version, undefined, 'tagged'); + expect(cleanup.readme).toEqual(''); + }); + + test('should keep readme (version in dist-tag)', () => { + const manifest = generatePackageMetadata('foo'); + const version = manifest.versions['1.0.0']; + const cleanup = cleanUpReadme(version, manifest[DIST_TAGS], 'tagged'); + expect(cleanup.readme).toEqual('# test'); + }); + }); + + describe('should keep all readmes', () => { + test('should keep readme (no dist-tags)', () => { + const manifest = generatePackageMetadata('foo'); + const version = manifest.versions['1.0.0']; + const cleanup = cleanUpReadme(version, undefined, 'all'); + expect(cleanup.readme).toEqual('# test'); + }); + + test('should keep readme (version in dist-tag)', () => { + const manifest = generatePackageMetadata('foo'); + const version = manifest.versions['1.0.0']; + const cleanup = cleanUpReadme(version, manifest[DIST_TAGS], 'all'); + expect(cleanup.readme).toEqual('# test'); + }); + }); + }); }); diff --git a/packages/store/test/storage.spec.ts b/packages/store/test/storage.spec.ts index 5db9eac53..16369c7ae 100644 --- a/packages/store/test/storage.spec.ts +++ b/packages/store/test/storage.spec.ts @@ -176,7 +176,7 @@ describe('storage', () => { }); expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.0' }); // verdaccio keeps latest version of readme on manifest level but not by version - expect(manifest.versions['1.0.0'].readme).not.toBeDefined(); + expect(manifest.versions['1.0.0'].readme).toEqual(''); expect(manifest.readme).toEqual('# test'); expect(manifest._attachments).toEqual({}); expect(typeof manifest._rev).toBeTruthy(); @@ -388,7 +388,7 @@ describe('storage', () => { })) as Manifest; // verdaccio keeps latest version of readme on manifest level but not by version - expect(manifest.versions['1.0.0'].readme).not.toBeDefined(); + expect(manifest.versions['1.0.0'].readme).toEqual(''); expect(manifest.readme).toEqual('# test'); }); });