npm publish - removing scripts from package.json?

Short answer.

"Would this be possible to kind of remove scripts before publishing the package ?"

npm does not include a built-in feature to remove scripts from package.json.


Long answer with solution.

"Is this feasible? Or should I be thinking about another way ?"

There are a couple of built-in features known as Pre and Post hooks which can be utilized to meet your requirement, albeit in a rather custom way. The pertinent hooks are prepublish and postpublish and are described in the documentation as follows;

prepublish: Run BEFORE the package is packed and published, as well as on local npm install without any arguments...

postpublish: Run AFTER the package is published.

A synopsis of the solution is:

  1. Utilize a prepublish script in your projects package.json to invoke a custom nodejs script. This nodejs script performs the following:

    • Reads the original package.json data and caches it.
    • Removes specific scripts/keys from the scripts section of package.json.
    • Writes the revised data back to the original package.json.
  2. Utilize a postpublish script in your projects package.json to invoke another custom nodejs script. This secondary nodejs script performs the following:

    • Reverts the content of package.json back to it's original state.

Code examples/gists.

  1. The following nodejs script will carry out the tasks mentioned in point one above. Let's name it cleanse-pkg.js.

    cleanse-pkg.js

    const fs = require('fs');
    const path = require('path');
    
    // Define absolute paths for original pkg and temporary pkg.
    const ORIG_PKG_PATH = path.resolve(__dirname, '../package.json');
    const CACHED_PKG_PATH = path.resolve(__dirname, '../../cached-package.json');
    
    // Obtain original `package.json` contents.
    const pkgData = require(ORIG_PKG_PATH);
    
    if (process.argv.length <= 2) {
      throw new Error('Missing npm scripts key/name argument(s)');
    }
    
    // Get list of arguments passed to script.
    const scriptsToRemove = process.argv[2].split(',');
    const devDepsToRemove = process.argv[3] ? process.argv[3].split(',') : [];
    
    // Write/cache the original `package.json` data to `cached-package.json` file.
    fs.writeFile(CACHED_PKG_PATH, JSON.stringify(pkgData), function (err) {
      if (err) throw err;
    });
    
    // Remove the specified named scripts from the scripts section.
    scriptsToRemove.forEach(function (scriptName) {
      delete pkgData.scripts[scriptName];
    });
    
    // Remove the specified named pkgs from the devDependencies section.
    devDepsToRemove.forEach(function (pkgName) {
      delete pkgData.devDependencies[pkgName];
    });
    
    // Overwrite original `package.json` with new data (i.e. minus the specific data).
    fs.writeFile(ORIG_PKG_PATH, JSON.stringify(pkgData, null, 2), function (err) {
      if (err) throw err;
    });
    
  2. The following secondary nodejs script will carry out the task mentioned in point two above. Let's name this one restore-pkg.js.

    restore-pkg.js

    const fs = require('fs');
    const path = require('path');
    
    // Define absolute paths for original pkg and temporary pkg.
    const ORIG_PKG_PATH = path.resolve(__dirname, '../package.json');
    const CACHED_PKG_PATH = path.resolve(__dirname, '../../cached-package.json');
    
    // Obtain original/cached contents from `cached-package.json`.
    const pkgData = JSON.stringify(require(CACHED_PKG_PATH), null, 2) + '\n';
    
    // Write data from `cached-package.json` back to original `package.json`.
    fs.writeFile(ORIG_PKG_PATH, pkgData, function (err) {
      if (err) throw err;
    });
    
    // Delete the temporary `cached-package.json` file.
    fs.unlink(CACHED_PKG_PATH, function (err) {
      if (err) throw err;
    });
    

Implementation and Usage.

  1. The prepublish and postpublish scripts are defined in the projects package.json as follows:

    Contrived original package.json

    {
      ...
      "scripts": {
        "keep": ... ,
        "a": ... ,
        "b": ... ,
        "prepublish": "node .scripts/cleanse-pkg \"a,b,prepublish,postpublish\"",
        "postpublish": "node .scripts/restore-pkg"
      },
      ...
    }
    
    • Note the \"a,b,prepublish,postpublish\" part in the prepublish script. This defines the argument to pass to cleanse-pkg.js (i.e. it lists the names of each script to be removed before publishing). Each named script to be removed must be; provided as a single string, be separated with commas, and must not include spaces.

    • Both cleanse-pkg.js and restore-pkg.js reside in a hidden folder named .scripts, which itself resides at the top level of the projects directory, (i.e. at the same level as the projects package.json). Both nodejs scripts can be relocated as preferred, and the paths to them redefined as necessary in the respective npm-script .

  2. Given the contrived package.json above, the actual package.json contents in the resultant published tarball will be as follows:

    Resultant/published package.json

    {
      ...
      "scripts": {
        "keep": ...
      },
      ...
    }
    

Packages in the devDependencies section.

Maybe there are packages listed in the devDependencies section of your projects package.json that you also want removed in the published package.json.

(Note: Any packages listed in the devDependencies section are not downloaded when the user installs via the npm-registry though).

However, perhaps you'd like to remove them anyway. If that's a requirement, then cleanse-pkg.js also accepts an optional second argument. This argument is analogous to the first argument, whereby each named package to be removed from the devDependencies section must be; provided as a single string, be separated with commas, and must not include spaces.

  1. Let's assume the original package.json is as follows this time:

    Contrived original package.json

    {
      ...
      "scripts": {
        "keep": ... ,
        "a": ... ,
        "b": ... ,
        "prepublish": "node .scripts/cleanse-pkg \"a,b,prepublish,postpublish\" \"x,z\"",
        "postpublish": "node .scripts/restore-pkg"
      },
      "devDependencies": {
        "x": "^1.0.2",
        "y": "^0.8.1",
        "z": "^0.8.1"
      },
      ...
    }
    
    • Note the additional second \"x,z\" argument added to the prepublish script to specify which packages to omit from the devDependecies section.
  2. This time, given the contrived package.json above, the actual package.json contents in the resultant published tarball will be as follows:

    Resultant/published package.json

    {
      ...
      "scripts": {
        "keep": ...
      },
      "devDependencies": {
        "y": "^0.8.1"
      },
      ...
    }
    

Running

This solution assumes npm publish will be run utlizing one of the following methods:

  1. From within the project directory without any argument(s). E.g. npm publish.
  2. Specifying a the path to your projects package.json. E.g. npm publish path/to/package.json.

This will not work by providing a url or file path to a gzipped tar archive containing a single folder with a package.json file inside.


Additional Notes

  1. To prevent both utility nodejs scripts, (cleanse-pkg.js and restore-pkg.js), from being published they should be added to your projects .npmignore file. Given the location of both these files explained above you can add a .scripts entry in .npmignore.

  2. Both utility nodejs scripts, cleanse-pkg.js and restore-pkg.js, define:

    • An absolute path to the original package.json file.
    • An absolute path to where the temporary cached-package.json file should be written. Currently this is saved one-level-up/outside from the project directory to avoid it being published).

    If you choose to store cleanse-pkg.js and restore-pkg.js to a different location than the one described above, the paths in following code snippet will need to redefined as necessary in both files.

    const ORIG_PKG_PATH = path.resolve(__dirname, '../package.json');
    const CACHED_PKG_PATH = path.resolve(__dirname, '../../cached-package.json');
    

You might consider this package as well. https://www.npmjs.com/package/clean-publish

Specifically, they provide a tool for working only with the packakge.json.

$ npm run clear-package-json package.json -o package/package.json --fields scripts name
# or
$ npm run clear-package-json package.json > package/package.json
# or
$ cat package.json | npm run clear-package-json
# `fields` also will be getted from config file

It seems to do exactly what you're requesting, but also a lot more. It does seem highly configurable.


Update

Here's a package I've created which does what you're requesting, but in a significantly different manner.

https://www.npmjs.com/package/clean-package

$ clean-package --remove scripts