diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs index 42bd213ba473e..7d349fc629401 100644 --- a/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -67,6 +67,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "git-tag-version": true, "global": false, "globalconfig": "{CWD}/global/etc/npmrc", + "global-ignore-file": "{CWD}/global/etc/npmignore", "global-style": false, "heading": "npm", "https-proxy": null, @@ -247,6 +248,7 @@ fund = true git = "git" git-tag-version = true global = false +global-ignore-file = "{CWD}/global/etc/npmignore" global-style = false globalconfig = "{CWD}/global/etc/npmrc" heading = "npm" diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index e7bad30e38ff4..5c8209ac09387 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -797,6 +797,25 @@ folder instead of the current working directory. See +#### \`global-ignore-file\` + +* Default: The global --prefix setting plus 'etc/npmignore'. For example, + '/usr/local/etc/npmignore' +* Type: Path + +An additional ignore file applied during \`npm pack\` and \`npm publish\`, owned +by the current user rather than the package. Patterns follow the same syntax +as a package's local \`.npmignore\` file. Useful for keeping editor metadata +(such as \`.idea/\` or \`*.iml\`) and scratch directories out of every package +you publish, without adding them to each package's own ignore rules. + +The global rules apply in addition to a package's local \`.npmignore\`. When a +package uses a \`files\` field in its \`package.json\`, an entry in \`files\` that +contradicts a global rule (i.e., explicitly includes a path the global rule +would exclude) still wins. + + + #### \`globalconfig\` * Default: The global --prefix setting plus 'etc/npmrc'. For example, @@ -2349,6 +2368,7 @@ Array [ "git-tag-version", "global", "globalconfig", + "global-ignore-file", "global-style", "heading", "https-proxy", @@ -2526,6 +2546,7 @@ Array [ "git-tag-version", "global", "globalconfig", + "global-ignore-file", "global-style", "heading", "https-proxy", @@ -2700,6 +2721,7 @@ Object { "gitTagVersion": true, "global": false, "globalconfig": "{CWD}/global/etc/npmrc", + "globalIgnoreFile": "{CWD}/global/etc/npmignore", "heading": "npm", "httpsProxy": null, "ifPresent": false, diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index 991d219a3f459..e640ce84b35df 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -887,6 +887,29 @@ const definitions = { `, flatten, }), + // the global-ignore-file has its default defined outside of this module + 'global-ignore-file': new Definition('global-ignore-file', { + type: path, + default: '', + defaultDescription: ` + The global --prefix setting plus 'etc/npmignore'. For example, + '/usr/local/etc/npmignore' + `, + description: ` + An additional ignore file applied during \`npm pack\` and \`npm + publish\`, owned by the current user rather than the package. Patterns + follow the same syntax as a package's local \`.npmignore\` file. + Useful for keeping editor metadata (such as \`.idea/\` or \`*.iml\`) + and scratch directories out of every package you publish, without + adding them to each package's own ignore rules. + + The global rules apply in addition to a package's local \`.npmignore\`. + When a package uses a \`files\` field in its \`package.json\`, an entry + in \`files\` that contradicts a global rule (i.e., explicitly includes + a path the global rule would exclude) still wins. + `, + flatten, + }), 'global-style': new Definition('global-style', { default: false, type: Boolean, diff --git a/workspaces/config/lib/index.js b/workspaces/config/lib/index.js index a1acb7969b29f..5db9cf609947f 100644 --- a/workspaces/config/lib/index.js +++ b/workspaces/config/lib/index.js @@ -313,6 +313,24 @@ class Config { configurable: true, enumerable: true, }) + + // like globalconfig, the global-ignore-file default is computed from + // the current prefix. since prefix may be overridden after defaults + // load (via cli, env, or userconfig), expose a getter and only freeze + // to a value once explicitly set. + Object.defineProperty(data, 'global-ignore-file', { + get: () => resolve(this.#get('prefix'), 'etc/npmignore'), + set (value) { + Object.defineProperty(data, 'global-ignore-file', { + value, + configurable: true, + writable: true, + enumerable: true, + }) + }, + configurable: true, + enumerable: true, + }) } loadHome () { diff --git a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs index 78445376b9ef1..023b272840d7f 100644 --- a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs +++ b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs @@ -210,6 +210,9 @@ Object { "global": Array [ "boolean value (true or false)", ], + "global-ignore-file": Array [ + "valid filesystem path", + ], "global-style": Array [ "boolean value (true or false)", ], diff --git a/workspaces/config/test/definitions/definitions.js b/workspaces/config/test/definitions/definitions.js index aa282ea665500..69c49c0204f9d 100644 --- a/workspaces/config/test/definitions/definitions.js +++ b/workspaces/config/test/definitions/definitions.js @@ -1050,3 +1050,19 @@ t.test('node-gyp', t => { t.end() }) + +t.test('global-ignore-file', t => { + const defs = mockDefs() + const def = defs['global-ignore-file'] + + t.ok(def, 'global-ignore-file definition is exported') + t.equal(def.type, require('../../lib/type-defs.js').path.type, 'is a path typed config') + t.equal(def.default, '', 'default value is empty (computed at load time)') + t.ok(/ignore/i.test(def.description), 'has a descriptive entry') + + const flat = {} + def.flatten('global-ignore-file', { 'global-ignore-file': '/path/to/npmignore' }, flat) + t.strictSame(flat, { globalIgnoreFile: '/path/to/npmignore' }, 'flattens to camelCase') + + t.end() +}) diff --git a/workspaces/config/test/index.js b/workspaces/config/test/index.js index 7a166047f4e48..bf8bd8625f416 100644 --- a/workspaces/config/test/index.js +++ b/workspaces/config/test/index.js @@ -1869,3 +1869,46 @@ t.test('before and min-release-age', async t => { t.ok(config.flat.before < Date.now(), 'before date is in the past not the future') t.equal(config.get('min-release-age'), 30, 'min-release-age config remains readable after flattening') }) + +t.test('global-ignore-file defaults to ${prefix}/etc/npmignore', async t => { + const path = t.testdir() + const config = new Config({ + npmPath: `${path}/npm`, + env: {}, + argv: [process.execPath, __filename, '--prefix', `${path}/global`], + cwd: path, + definitions, + shorthands, + flatten, + }) + await config.load() + t.equal( + config.get('global-ignore-file'), + resolve(`${path}/global/etc/npmignore`), + 'computed from --prefix, mirrors globalconfig' + ) + t.equal(config.flat.globalIgnoreFile, resolve(`${path}/global/etc/npmignore`), 'flattens to camelCase') +}) + +t.test('global-ignore-file follows an explicit override', async t => { + const path = t.testdir() + const config = new Config({ + npmPath: `${path}/npm`, + env: {}, + argv: [ + process.execPath, __filename, + '--prefix', `${path}/global`, + '--global-ignore-file', `${path}/custom/.npmignore`, + ], + cwd: path, + definitions, + shorthands, + flatten, + }) + await config.load() + t.equal( + config.get('global-ignore-file'), + resolve(`${path}/custom/.npmignore`), + 'cli override wins over computed default' + ) +})