diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs index ec4841813d025..382f6d29d6ab4 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, @@ -244,6 +245,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 5e626914b5124..eeabfa66c8927 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -804,6 +804,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, @@ -2358,6 +2377,7 @@ Array [ "git-tag-version", "global", "globalconfig", + "global-ignore-file", "global-style", "heading", "https-proxy", @@ -2535,6 +2555,7 @@ Array [ "git-tag-version", "global", "globalconfig", + "global-ignore-file", "global-style", "heading", "https-proxy", @@ -2709,6 +2730,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 15db87f6b4831..605ffb912457e 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -892,6 +892,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 2aae7efbefb09..4121c2a7a3840 100644 --- a/workspaces/config/lib/index.js +++ b/workspaces/config/lib/index.js @@ -317,6 +317,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 b5f59aab5768c..18e52fad393c3 100644 --- a/workspaces/config/test/index.js +++ b/workspaces/config/test/index.js @@ -2315,3 +2315,46 @@ t.test('CLI --min-release-age beats env npm_config_min_release_age', async t => 'CLI --min-release-age=3 overrides env npm_config_min_release_age=30' ) }) + +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' + ) +})