11

Before publishing my script, I have a number of scripts under the package.json to compile coffeescript, typescript and developer only commands - which make no sense once it's published.

I was wondering if there is a procedure for removing certain scripts under the package.json ? Considering that when publishing your package, it also publishes package.json.

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

Once I publish my script, I remove a lot of the typescript and coffeescript source files (as they have been compiled), so a script I have for building no makes no sense for the published package.

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

halfer
  • 19,824
  • 17
  • 99
  • 186
Martin
  • 23,844
  • 55
  • 201
  • 327

3 Answers3

14

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');
    
RobC
  • 22,977
  • 20
  • 73
  • 80
  • In your example, shouldn't the script `postpublish` not run at all since it is removed from the package.json scripts during `prepublish`? – Matei Radu Oct 14 '18 at 12:45
  • @MateiRadu - Good question. At time of posting answer _pkg.json_ was read once and `pre` and `postpublish` scripts were cached early, i.e. before `prepublish` and `publish` were run. However looking at [this commit](https://github.com/npm/cli/commit/674004c4c5ef50ed303add582351b32e2293b78e#diff-6) (which is more recent that posting), my suspicion is that _"may"_ no longer be the case. npm seems to now be reading _pkg.json_ at every step of _publish_ process. Try running `npm publish --dry-run`, (i.e include `--dry-run` options) and check results. Would be great if you confirm here in comments. – RobC Oct 14 '18 at 17:59
  • 1
    I can confirm that for each hook package.json is read again so this approach no longer works. For now, my only solution is to use `npm pack` to create the tarball and a `postpack` script that edits the package.json inside the created tarball so that the origianl pkg is not altered. – Matei Radu Oct 14 '18 at 18:31
  • 1
    don't know if it might help but this is my current [postpack](https://github.com/matt-block/react-native-in-app-browser/blob/master/scripts/postpack.ts) script. – Matei Radu Oct 14 '18 at 18:36
9

As of npm@7, the command npm-pkg can perform a delete of keys from package.json and does not need any external package to download or execute.

# entire scripts dictionary
$ npm pkg delete scripts

# delete specific script using the subkey syntax
$ npm pkg delete scripts.build

The documentation for the command is: https://docs.npmjs.com/cli/v8/commands/npm-pkg

If you want a backup, then clean-package can provide this automatic backup, or just do it manually with:

$ cp package.json package.json.bkup
codejedi365
  • 1,074
  • 9
  • 13
3

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
roydukkey
  • 3,149
  • 2
  • 27
  • 43