Search code examples
typescriptnpmglobnpm-publish

npm will not ignore my TypeScript source code file no matter what I try


I have an open source project that is written in TypeScript, and is compiled to JavaScript and then published as a CLI tool via npm publish.

I've been unable to exclude the TypeScript source code from the npm package, and there are other *.ts files that seem to get included at random (but we'll focus on one file here).

I do not have a problem including the TypeScript source files, but that's just adding extra bytes to the package for something the user will never need.

I can reproduce the problem with a very small setup

package.json

{
    "name": "example",
    "version": "1.0.0",
    "main": "readme.js"
}

I've tried a very aggressive ignore list:

.npmignore

**/*
*.ts
**
*
*.*
readme.ts

Create some source code files

touch readme.ts
touch readme.js

Now if I try to generate a npm package it will include the readme.ts file.

$ npm pack
npm notice
npm notice package: example@1.0.0
npm notice === Tarball Contents ===
npm notice 0   readme.js
npm notice 76B package.json
npm notice 0   readme.ts
npm notice === Tarball Details ===
npm notice name:          example
npm notice version:       1.0.0
npm notice filename:      example-1.0.0.tgz
npm notice package size:  195 B
npm notice unpacked size: 76 B
npm notice shasum:        a1ee7cc6b7b0d1e2eccf870e95d7df38c5dcb609
npm notice integrity:     sha512-ppNEfKEvT9DEP[...]+d16IWbrj7OdA==
npm notice total files:   3
npm notice
example-1.0.0.tgz

So why is it including readme.ts?

I have all files excluded, and only the readme.js is referenced in the package.json file.

$ npm --version
6.13.1

Solution

  • Short Answer: Files named readme.* (e.g. readme.foo, readme.quux, etc) will be included by npm regardless of how you try to negate it in your .npmignore file.


    NPM's test file:

    In one of npm's tests for npm-cli titled "certain files included unconditionally" starting at line #511 they include a test for a file named readme.randomext.

    From observing this test we can conclude that any file named readme.* is going to be included in the tarball (.tgz) file, even though it may be specified in the .npmignore file.

    Below is an excerpt from the aforementioned NPM test file named pack-files-and-ignores.js

    test('certain files included unconditionally', function (t) {
      var fixture = new Tacks(
        Dir({
          'package.json': File({
            name: 'npm-test-files',
            version: '1.2.5'
          }),
          '.npmignore': File(
            'package.json',
            'README',
            'Readme',
            'readme.md',
            'readme.randomext',   // <----
            'changelog',
            'CHAngelog',
            'ChangeLOG.txt',
            'history',
            'HistorY',
            'HistorY.md',
            'license',
            'licence',
            'LICENSE',
            'LICENCE'
          ),
          'README': File(''),
          'Readme': File(''),
          'readme.md': File(''),
          'readme.randomext': File(''),     // <----
          'changelog': File(''),
          'CHAngelog': File(''),
          'ChangeLOG.txt': File(''),
          'history': File(''),
          'HistorY': File(''),
          'HistorY.md': File(''),
          'license': File(''),
          'licence': File(''),
          'LICENSE': File(''),
          'LICENCE': File('')
        })
      )
      withFixture(t, fixture, function (done) {
        t.ok(fileExists('package.json'), 'package.json included')
        t.ok(fileExists('README'), 'README included')
        t.ok(fileExists('Readme'), 'Readme included')
        t.ok(fileExists('readme.md'), 'readme.md included')
        t.ok(fileExists('readme.randomext'), 'readme.randomext included')    // <----
        t.ok(fileExists('changelog'), 'changelog included')
        t.ok(fileExists('CHAngelog'), 'CHAngelog included')
        t.ok(fileExists('ChangeLOG.txt'), 'ChangeLOG.txt included')
        t.ok(fileExists('license'), 'license included')
        t.ok(fileExists('licence'), 'licence included')
        t.ok(fileExists('LICENSE'), 'LICENSE included')
        t.ok(fileExists('LICENCE'), 'LICENCE included')
        done()
      })
    })
    

    Workarounds:

    There are a couple if workarounds you could consider, such as:

    1. The simplest is to rename the file to e.g. read-me.ts and then specify read-me.ts in your .npmignore file. However, as you have *.ts currently specified in your .npmignore it's not actually necessary for you to additionally specify read-me.ts too.

    2. Or, if that's not possible consider:

      • Add a postpack script to the scripts section of package.json. For instance:

        "scripts": {
          "postpack": "node ./fix-it.js",
          ...
        },
        
      • Then in fix-it.js utilize the node-tar package to:

        1. Unpack the tarball (.tgz).
        2. Remove the unwanted file(s), i.e. readme.ts.
        3. Repack to create a new tarball (.tgz).
      • Steps to publishing your package then become:

        1. Run npm pack

          The postpack script will subsequently auto generate the new .tgz file without the unwanted file(s).

        2. Run npm publish ./path/to/mypackage.tgz