From c87260a44158c667d92c52a755e937d1869d4487 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Fri, 24 May 2024 19:23:19 +0300 Subject: [PATCH] refactor: add `normalizePackageMan` helper (#100) ## What / Why Aligns normalization logic with `directories.bin` See also: https://github.com/npm/normalize-package-data/blob/main/lib/fixer.js#L105 ```js fixManField: function (data) { if (!data.man) { return } if (typeof data.man === 'string') { data.man = [data.man] } }, ``` ## References * continues https://github.com/npm/read-package-json/pull/177 * relates https://github.com/npm/package-json/pull/104 CC @wraithgar --- lib/normalize.js | 42 +++++++++++++++--- test/prepare.js | 112 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 20 deletions(-) diff --git a/lib/normalize.js b/lib/normalize.js index 0337a05..dc21784 100644 --- a/lib/normalize.js +++ b/lib/normalize.js @@ -83,11 +83,36 @@ function normalizePackageBin (pkg, changes) { delete pkg.bin } +function normalizePackageMan (pkg, changes) { + if (pkg.man) { + const mans = [] + for (const man of (Array.isArray(pkg.man) ? pkg.man : [pkg.man])) { + if (typeof man !== 'string') { + changes?.push(`removed invalid "man [${man}]"`) + } else { + mans.push(unixifyPath(securePath(unixifyPath(man)))) + } + } + + if (!mans.length) { + changes?.push('empty "man" was removed') + } else { + pkg.man = mans + return pkg + } + } + delete pkg.man +} + function isCorrectlyEncodedName (spec) { return !spec.match(/[/@\s+%:]/) && spec === encodeURIComponent(spec) } +function unixifyPath (ref) { + return ref.replace(/\\|:/g, '/') +} + function isValidScopedPackageName (spec) { if (spec.charAt(0) !== '@') { return false @@ -329,13 +354,16 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) } // expand directories.man - if (steps.includes('mans') && !data.man && data.directories?.man) { - const manDir = data.directories.man - const cwd = path.resolve(pkg.path, securePath(manDir)) - const files = await lazyLoadGlob()('**/*.[0-9]', { cwd }) - data.man = files.map(man => - path.relative(pkg.path, path.join(cwd, man)).split(path.sep).join('/') - ) + if (steps.includes('mans')) { + if (data.directories?.man && !data.man) { + const manDir = unixifyPath(securePath(unixifyPath(data.directories.man))) + const cwd = path.resolve(pkg.path, manDir) + const files = await lazyLoadGlob()('**/*.[0-9]', { cwd }) + data.man = files.map(man => + path.relative(pkg.path, path.join(cwd, man)).split(path.sep).join('/') + ) + } + normalizePackageMan(data, changes) } if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) { diff --git a/test/prepare.js b/test/prepare.js index 4f8414e..5421efe 100644 --- a/test/prepare.js +++ b/test/prepare.js @@ -154,6 +154,105 @@ for (const [name, testPrepare] of Object.entries(testMethods)) { t.end() }) + t.test('man', t => { + t.test('resolves directory', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + directories: { man: './man' }, + }), + man: { man1: { 'test.1': 'man test file' } }, + })) + t.strictSame(content.man, ['man/man1/test.1']) + }) + + if (name === '@npmcli/package-json') { + t.test('non-string', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + man: 123, + }), + })) + t.has(content, { man: undefined }) + }) + + t.test('good', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + man: './man/test.1', + }), + })) + t.strictSame(content.man, ['man/test.1']) + }) + + t.test('empty', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + man: [], + }), + })) + t.has(content, { man: undefined }) + }) + + t.test('directories.man no prefix', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + name: 'man-test', + directories: { + man: './man', + }, + }), + man: { 'test.1': '.TH man "test man page"' }, + })) + t.strictSame(content.man, ['man/test.1']) + }) + + t.test('directories.man trim prefix', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + name: 'man-test', + directories: { + man: '../../../../../man', + }, + }), + man: { 'test.1': '.TH man "test man page"' }, + })) + t.strictSame(content.man, ['man/test.1']) + }) + + t.test('directories.man handles reversed slashes', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + name: 'man-test', + directories: { + man: '..\\..\\man', + }, + }), + man: { 'test.1': '.TH man "test man page"' }, + })) + t.strictSame(content.man, ['man/test.1']) + }) + + t.test('directories.man with man', async t => { + const { content } = await testPrepare(t, ({ + 'package.json': JSON.stringify({ + name: 'man-test', + directories: { + man: './man', + }, + man: '../../test.2', + }), + man: { + 'test.1': '.TH man "test man page 1"', + 'test.2': '.TH man "test man page 2"', + }, + })) + t.strictSame(content.man, ['test.2']) + }) + } + + t.end() + }) + t.test('bundleDependencies', t => { t.test('true', async t => { const { content } = await testPrepare(t, ({ @@ -325,19 +424,6 @@ for (const [name, testPrepare] of Object.entries(testMethods)) { t.end() }) - t.test('man', t => { - t.test('resolves directory', async t => { - const { content } = await testPrepare(t, ({ - 'package.json': JSON.stringify({ - directories: { man: './man' }, - }), - man: { man1: { 'test.1': 'man test file' } }, - })) - t.strictSame(content.man, ['man/man1/test.1']) - }) - t.end() - }) - t.test('gitHead', t => { t.test('HEAD with no ref', async t => { const { content } = await testPrepare(t, ({