├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── appveyor.yml
├── index.js
├── lib
└── spawn.js
├── node-gyp-bin
├── node-gyp
└── node-gyp.cmd
├── package-lock.json
├── package.json
├── tap-snapshots
├── test-get-spawn-args.js-TAP.test.js
└── test-path-munging.js-TAP.test.js
└── test
├── fixtures
├── count-to-10-working-postinstall
│ ├── package.json
│ └── postinstall.js
├── count-to-10
│ ├── package.json
│ └── postinstall.js
└── has-hooks
│ ├── node_modules
│ └── .hooks
│ │ └── postinstall
│ └── package.json
├── get-spawn-args.js
├── index.js
└── path-munging.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.nyc_output
3 | /coverage
4 | /test/cache
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | node_js:
4 | - "12"
5 | - "10"
6 | - "8"
7 | - "6"
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | ## [3.1.5](https://github.com/npm/lifecycle/compare/v3.1.4...v3.1.5) (2020-03-26)
7 |
8 |
9 |
10 |
11 | ## [3.1.4](https://github.com/npm/lifecycle/compare/v3.1.3...v3.1.4) (2019-09-18)
12 |
13 |
14 | ### Bug Fixes
15 |
16 | * filter functions and undefined out of makeEnv ([10c0c08](https://github.com/npm/lifecycle/commit/10c0c08))
17 |
18 |
19 |
20 |
21 | ## [3.1.3](https://github.com/npm/lifecycle/compare/v3.1.2...v3.1.3) (2019-08-12)
22 |
23 |
24 | ### Bug Fixes
25 |
26 | * fail properly if uid-number raises an error ([e0e1b62](https://github.com/npm/lifecycle/commit/e0e1b62))
27 |
28 |
29 |
30 |
31 | ## [3.1.2](https://github.com/npm/lifecycle/compare/v3.1.1...v3.1.2) (2019-07-22)
32 |
33 |
34 | ### Bug Fixes
35 |
36 | * do not exclude /path/ from process.env copying ([53e6318](https://github.com/npm/lifecycle/commit/53e6318))
37 |
38 |
39 |
40 |
41 | # [3.1.0](https://github.com/npm/lifecycle/compare/v3.0.0...v3.1.0) (2019-07-17)
42 |
43 |
44 | ### Bug Fixes
45 |
46 | * remove procInterrupt listener on SIGINT in procError ([ea18ed2](https://github.com/npm/lifecycle/commit/ea18ed2)), closes [#36](https://github.com/npm/lifecycle/issues/36) [#11](https://github.com/npm/lifecycle/issues/11) [#18](https://github.com/npm/lifecycle/issues/18)
47 | * set only one PATH env variable for child proc ([3aaf954](https://github.com/npm/lifecycle/commit/3aaf954)), closes [#22](https://github.com/npm/lifecycle/issues/22) [#25](https://github.com/npm/lifecycle/issues/25)
48 |
49 |
50 | ### Features
51 |
52 | * **process.env.path:** Use platform specific path casing if present ([5523951](https://github.com/npm/lifecycle/commit/5523951)), closes [#29](https://github.com/npm/lifecycle/issues/29) [#30](https://github.com/npm/lifecycle/issues/30)
53 |
54 |
55 |
56 |
57 | # [3.0.0](https://github.com/npm/lifecycle/compare/v2.1.1...v3.0.0) (2019-07-10)
58 |
59 |
60 | * node-gyp@5.0.2 ([3c5aae6](https://github.com/npm/lifecycle/commit/3c5aae6))
61 |
62 |
63 | ### BREAKING CHANGES
64 |
65 | * requires modifying the version of node-gyp in npm cli.
66 |
67 | Required for https://github.com/npm/cli/pull/208
68 | Fix: https://github.com/npm/npm-lifecycle/issues/37
69 | Close: https://github.com/npm/npm-lifecycle/issues/38
70 | PR-URL: https://github.com/npm/npm-lifecycle/pull/38
71 | Credit: @irega
72 | Reviewed-by: @isaacs
73 |
74 |
75 |
76 |
77 | ## [2.1.1](https://github.com/npm/lifecycle/compare/v2.1.0...v2.1.1) (2019-05-08)
78 |
79 |
80 | ### Bug Fixes
81 |
82 | * **test:** update postinstall script for fixture ([220cd70](https://github.com/npm/lifecycle/commit/220cd70))
83 |
84 |
85 |
86 |
87 | # [2.1.0](https://github.com/npm/lifecycle/compare/v2.0.3...v2.1.0) (2018-08-13)
88 |
89 |
90 | ### Bug Fixes
91 |
92 | * **windows:** revert writing all possible cases of PATH variables ([#22](https://github.com/npm/lifecycle/issues/22)) ([8fcaa21](https://github.com/npm/lifecycle/commit/8fcaa21)), closes [#20](https://github.com/npm/lifecycle/issues/20)
93 |
94 |
95 | ### Features
96 |
97 | * **ci:** add node@10 to CI ([e206aa0](https://github.com/npm/lifecycle/commit/e206aa0))
98 |
99 |
100 |
101 |
102 | ## [2.0.3](https://github.com/npm/lifecycle/compare/v2.0.2...v2.0.3) (2018-05-16)
103 |
104 |
105 |
106 |
107 | ## [2.0.2](https://github.com/npm/lifecycle/compare/v2.0.1...v2.0.2) (2018-05-16)
108 |
109 |
110 | ### Bug Fixes
111 |
112 | * **hooks:** run .hooks scripts even if package.json script is not present ([#13](https://github.com/npm/lifecycle/issues/13)) ([67adc2d](https://github.com/npm/lifecycle/commit/67adc2d))
113 | * **windows:** Write to all possible cases of PATH variables ([#17](https://github.com/npm/lifecycle/issues/17)) ([e4ecc54](https://github.com/npm/lifecycle/commit/e4ecc54))
114 |
115 |
116 |
117 |
118 | ## [2.0.1](https://github.com/npm/lifecycle/compare/v2.0.0...v2.0.1) (2018-03-08)
119 |
120 |
121 | ### Bug Fixes
122 |
123 | * **log:** Fix formatting of invalid wd warning ([#12](https://github.com/npm/lifecycle/issues/12)) ([ced38f3](https://github.com/npm/lifecycle/commit/ced38f3))
124 |
125 |
126 |
127 |
128 | # [2.0.0](https://github.com/npm/lifecycle/compare/v1.0.3...v2.0.0) (2017-11-17)
129 |
130 |
131 | ### Features
132 |
133 | * **node-gyp:** use own node-gyp ([ae94ed2](https://github.com/npm/lifecycle/commit/ae94ed2))
134 | * **nodeOptions:** add "nodeOptions" option to set NODE_OPTIONS for child ([#7](https://github.com/npm/lifecycle/issues/7)) ([2eb7a38](https://github.com/npm/lifecycle/commit/2eb7a38))
135 | * **stdio:** add child process io options and default logging of piped stdout/err ([#3](https://github.com/npm/lifecycle/issues/3)) ([7b8281a](https://github.com/npm/lifecycle/commit/7b8281a))
136 |
137 |
138 | ### BREAKING CHANGES
139 |
140 | * **node-gyp:** Previously you had to bring your own node-gyp AND you had
141 | to provide access the way npm does, by having a `bin` dir with a
142 | `node-gyp-bin` in it.
143 |
144 | Fixes: #4
145 |
146 |
147 |
148 |
149 | ## [1.0.3](https://github.com/npm/lifecycle/compare/v1.0.2...v1.0.3) (2017-09-01)
150 |
151 |
152 | ### Bug Fixes
153 |
154 | * **runCmd:** add missing option to runCmd recursive queue call ([1a69ce8](https://github.com/npm/lifecycle/commit/1a69ce8))
155 |
156 |
157 |
158 |
159 | ## [1.0.2](https://github.com/npm/lifecycle/compare/v1.0.1...v1.0.2) (2017-08-17)
160 |
161 |
162 |
163 |
164 | ## [1.0.1](https://github.com/npm/lifecycle/compare/v1.0.0...v1.0.1) (2017-08-16)
165 |
166 |
167 | ### Bug Fixes
168 |
169 | * **license:** fix up license documentation ([a784ca0](https://github.com/npm/lifecycle/commit/a784ca0))
170 |
171 |
172 |
173 |
174 | # 1.0.0 (2017-08-16)
175 |
176 |
177 | ### Bug Fixes
178 |
179 | * **misc:** use strict to fix node[@4](https://github.com/4) ([#2](https://github.com/npm/lifecycle/issues/2)) ([961ceb9](https://github.com/npm/lifecycle/commit/961ceb9))
180 |
181 |
182 | ### Features
183 |
184 | * **api:** Extract from npm proper ([#1](https://github.com/npm/lifecycle/issues/1)) ([27d9930](https://github.com/npm/lifecycle/commit/27d9930))
185 |
186 |
187 | ### BREAKING CHANGES
188 |
189 | * **api:** this is the initial implementation
190 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The npm application
2 | Copyright (c) npm, Inc. and Contributors
3 | Licensed on the terms of The Artistic License 2.0
4 |
5 | Node package dependencies of the npm application
6 | Copyright (c) their respective copyright owners
7 | Licensed on their respective license terms
8 |
9 | The npm public registry at https://registry.npmjs.org
10 | and the npm website at https://www.npmjs.com
11 | Operated by npm, Inc.
12 | Use governed by terms published on https://www.npmjs.com
13 |
14 | "Node.js"
15 | Trademark Joyent, Inc., https://joyent.com
16 | Neither npm nor npm, Inc. are affiliated with Joyent, Inc.
17 |
18 | The Node.js application
19 | Project of Node Foundation, https://nodejs.org
20 |
21 | The npm Logo
22 | Copyright (c) Mathias Pettersson and Brian Hammond
23 |
24 | "Gubblebum Blocky" typeface
25 | Copyright (c) Tjarda Koster, https://jelloween.deviantart.com
26 | Used with permission
27 |
28 |
29 | --------
30 |
31 |
32 | The Artistic License 2.0
33 |
34 | Copyright (c) 2000-2006, The Perl Foundation.
35 |
36 | Everyone is permitted to copy and distribute verbatim copies
37 | of this license document, but changing it is not allowed.
38 |
39 | Preamble
40 |
41 | This license establishes the terms under which a given free software
42 | Package may be copied, modified, distributed, and/or redistributed.
43 | The intent is that the Copyright Holder maintains some artistic
44 | control over the development of that Package while still keeping the
45 | Package available as open source and free software.
46 |
47 | You are always permitted to make arrangements wholly outside of this
48 | license directly with the Copyright Holder of a given Package. If the
49 | terms of this license do not permit the full use that you propose to
50 | make of the Package, you should contact the Copyright Holder and seek
51 | a different licensing arrangement.
52 |
53 | Definitions
54 |
55 | "Copyright Holder" means the individual(s) or organization(s)
56 | named in the copyright notice for the entire Package.
57 |
58 | "Contributor" means any party that has contributed code or other
59 | material to the Package, in accordance with the Copyright Holder's
60 | procedures.
61 |
62 | "You" and "your" means any person who would like to copy,
63 | distribute, or modify the Package.
64 |
65 | "Package" means the collection of files distributed by the
66 | Copyright Holder, and derivatives of that collection and/or of
67 | those files. A given Package may consist of either the Standard
68 | Version, or a Modified Version.
69 |
70 | "Distribute" means providing a copy of the Package or making it
71 | accessible to anyone else, or in the case of a company or
72 | organization, to others outside of your company or organization.
73 |
74 | "Distributor Fee" means any fee that you charge for Distributing
75 | this Package or providing support for this Package to another
76 | party. It does not mean licensing fees.
77 |
78 | "Standard Version" refers to the Package if it has not been
79 | modified, or has been modified only in ways explicitly requested
80 | by the Copyright Holder.
81 |
82 | "Modified Version" means the Package, if it has been changed, and
83 | such changes were not explicitly requested by the Copyright
84 | Holder.
85 |
86 | "Original License" means this Artistic License as Distributed with
87 | the Standard Version of the Package, in its current version or as
88 | it may be modified by The Perl Foundation in the future.
89 |
90 | "Source" form means the source code, documentation source, and
91 | configuration files for the Package.
92 |
93 | "Compiled" form means the compiled bytecode, object code, binary,
94 | or any other form resulting from mechanical transformation or
95 | translation of the Source form.
96 |
97 |
98 | Permission for Use and Modification Without Distribution
99 |
100 | (1) You are permitted to use the Standard Version and create and use
101 | Modified Versions for any purpose without restriction, provided that
102 | you do not Distribute the Modified Version.
103 |
104 |
105 | Permissions for Redistribution of the Standard Version
106 |
107 | (2) You may Distribute verbatim copies of the Source form of the
108 | Standard Version of this Package in any medium without restriction,
109 | either gratis or for a Distributor Fee, provided that you duplicate
110 | all of the original copyright notices and associated disclaimers. At
111 | your discretion, such verbatim copies may or may not include a
112 | Compiled form of the Package.
113 |
114 | (3) You may apply any bug fixes, portability changes, and other
115 | modifications made available from the Copyright Holder. The resulting
116 | Package will still be considered the Standard Version, and as such
117 | will be subject to the Original License.
118 |
119 |
120 | Distribution of Modified Versions of the Package as Source
121 |
122 | (4) You may Distribute your Modified Version as Source (either gratis
123 | or for a Distributor Fee, and with or without a Compiled form of the
124 | Modified Version) provided that you clearly document how it differs
125 | from the Standard Version, including, but not limited to, documenting
126 | any non-standard features, executables, or modules, and provided that
127 | you do at least ONE of the following:
128 |
129 | (a) make the Modified Version available to the Copyright Holder
130 | of the Standard Version, under the Original License, so that the
131 | Copyright Holder may include your modifications in the Standard
132 | Version.
133 |
134 | (b) ensure that installation of your Modified Version does not
135 | prevent the user installing or running the Standard Version. In
136 | addition, the Modified Version must bear a name that is different
137 | from the name of the Standard Version.
138 |
139 | (c) allow anyone who receives a copy of the Modified Version to
140 | make the Source form of the Modified Version available to others
141 | under
142 |
143 | (i) the Original License or
144 |
145 | (ii) a license that permits the licensee to freely copy,
146 | modify and redistribute the Modified Version using the same
147 | licensing terms that apply to the copy that the licensee
148 | received, and requires that the Source form of the Modified
149 | Version, and of any works derived from it, be made freely
150 | available in that license fees are prohibited but Distributor
151 | Fees are allowed.
152 |
153 |
154 | Distribution of Compiled Forms of the Standard Version
155 | or Modified Versions without the Source
156 |
157 | (5) You may Distribute Compiled forms of the Standard Version without
158 | the Source, provided that you include complete instructions on how to
159 | get the Source of the Standard Version. Such instructions must be
160 | valid at the time of your distribution. If these instructions, at any
161 | time while you are carrying out such distribution, become invalid, you
162 | must provide new instructions on demand or cease further distribution.
163 | If you provide valid instructions or cease distribution within thirty
164 | days after you become aware that the instructions are invalid, then
165 | you do not forfeit any of your rights under this license.
166 |
167 | (6) You may Distribute a Modified Version in Compiled form without
168 | the Source, provided that you comply with Section 4 with respect to
169 | the Source of the Modified Version.
170 |
171 |
172 | Aggregating or Linking the Package
173 |
174 | (7) You may aggregate the Package (either the Standard Version or
175 | Modified Version) with other packages and Distribute the resulting
176 | aggregation provided that you do not charge a licensing fee for the
177 | Package. Distributor Fees are permitted, and licensing fees for other
178 | components in the aggregation are permitted. The terms of this license
179 | apply to the use and Distribution of the Standard or Modified Versions
180 | as included in the aggregation.
181 |
182 | (8) You are permitted to link Modified and Standard Versions with
183 | other works, to embed the Package in a larger work of your own, or to
184 | build stand-alone binary or bytecode versions of applications that
185 | include the Package, and Distribute the result without restriction,
186 | provided the result does not expose a direct interface to the Package.
187 |
188 |
189 | Items That are Not Considered Part of a Modified Version
190 |
191 | (9) Works (including, but not limited to, modules and scripts) that
192 | merely extend or make use of the Package, do not, by themselves, cause
193 | the Package to be a Modified Version. In addition, such works are not
194 | considered parts of the Package itself, and are not subject to the
195 | terms of this license.
196 |
197 |
198 | General Provisions
199 |
200 | (10) Any use, modification, and distribution of the Standard or
201 | Modified Versions is governed by this Artistic License. By using,
202 | modifying or distributing the Package, you accept this license. Do not
203 | use, modify, or distribute the Package, if you do not accept this
204 | license.
205 |
206 | (11) If your Modified Version has been derived from a Modified
207 | Version made by someone other than you, you are nevertheless required
208 | to ensure that your Modified Version complies with the requirements of
209 | this license.
210 |
211 | (12) This license does not grant you the right to use any trademark,
212 | service mark, tradename, or logo of the Copyright Holder.
213 |
214 | (13) This license includes the non-exclusive, worldwide,
215 | free-of-charge patent license to make, have made, use, offer to sell,
216 | sell, import and otherwise transfer the Package with respect to any
217 | patent claims licensable by the Copyright Holder that are necessarily
218 | infringed by the Package. If you institute patent litigation
219 | (including a cross-claim or counterclaim) against any party alleging
220 | that the Package constitutes direct or contributory patent
221 | infringement, then this Artistic License to you shall terminate on the
222 | date that such litigation is filed.
223 |
224 | (14) Disclaimer of Warranty:
225 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
226 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
227 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
228 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
229 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
230 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
231 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
232 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
233 |
234 |
235 | --------
236 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Note: pending imminent deprecation
2 |
3 | **This module will be deprecated once npm v7 is released. Please do not rely
4 | on it more than absolutely necessary.**
5 |
6 | The lifecycle script runner used in npm v7 is
7 | [@npmcli/run-script](http://npm.im/@npmcli/run-script). Please use that
8 | module moving forward.
9 |
10 | -----
11 |
12 | # npm-lifecycle
13 |
14 | [`npm-lifecycle`](https://github.com/npm/npm-lifecycle) is a standalone library for
15 | executing packages' lifecycle scripts. It is extracted from npm itself and
16 | intended to be fully compatible with the way npm executes individual scripts.
17 |
18 | ## Install
19 |
20 | `$ npm install npm-lifecycle`
21 |
22 | ## Table of Contents
23 |
24 | * [Example](#example)
25 | * [Features](#features)
26 | * [Contributing](#contributing)
27 | * [API](#api)
28 | * [`lifecycle`](#lifecycle)
29 |
30 | ### Example
31 |
32 | ```javascript
33 | // idk yet
34 | ```
35 |
36 | ### API
37 |
38 | #### `> lifecycle(name, pkg, wd, [opts]) -> Promise`
39 |
40 | ##### Arguments
41 |
42 | * `opts.stdio` - the [stdio](https://nodejs.org/api/child_process.html#child_process_options_stdio)
43 | passed to the child process. `[0, 1, 2]` by default.
44 |
45 | ##### Example
46 |
47 | ```javascript
48 | lifecycle()
49 | ```
50 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: "10"
4 | - nodejs_version: "8"
5 | - nodejs_version: "6"
6 | - nodejs_version: "11"
7 |
8 | platform:
9 | - x64
10 |
11 | install:
12 | - ps: Install-Product node $env:nodejs_version $env:platform
13 | - npm config set spin false
14 | - npm i -g npm@latest
15 | - npm install
16 |
17 | test_script:
18 | - npm test
19 |
20 | matrix:
21 | fast_finish: true
22 |
23 | build: off
24 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | exports = module.exports = lifecycle
4 | exports.makeEnv = makeEnv
5 | exports._incorrectWorkingDirectory = _incorrectWorkingDirectory
6 |
7 | // for testing
8 | const platform = process.env.__TESTING_FAKE_PLATFORM__ || process.platform
9 | const isWindows = platform === 'win32'
10 | const spawn = require('./lib/spawn')
11 | const path = require('path')
12 | const Stream = require('stream').Stream
13 | const fs = require('graceful-fs')
14 | const chain = require('slide').chain
15 | const uidNumber = require('uid-number')
16 | const umask = require('umask')
17 | const which = require('which')
18 | const byline = require('byline')
19 | const resolveFrom = require('resolve-from')
20 |
21 | const DEFAULT_NODE_GYP_PATH = resolveFrom(__dirname, 'node-gyp/bin/node-gyp')
22 | const hookStatCache = new Map()
23 |
24 | let PATH = isWindows ? 'Path' : 'PATH'
25 | exports._pathEnvName = PATH
26 | const delimiter = path.delimiter
27 |
28 | // windows calls its path 'Path' usually, but this is not guaranteed.
29 | // merge them all together in the order they appear in the object.
30 | const mergePath = env =>
31 | Object.keys(env).filter(p => /^path$/i.test(p) && env[p])
32 | .map(p => env[p].split(delimiter))
33 | .reduce((set, p) => set.concat(p.filter(p => !set.includes(p))), [])
34 | .join(delimiter)
35 | exports._mergePath = mergePath
36 |
37 | const setPathEnv = (env, path) => {
38 | // first ensure that the canonical value is set.
39 | env[PATH] = path
40 | // also set any other case values, because windows.
41 | Object.keys(env)
42 | .filter(p => p !== PATH && /^path$/i.test(p))
43 | .forEach(p => { env[p] = path })
44 | }
45 | exports._setPathEnv = setPathEnv
46 |
47 | function logid (pkg, stage) {
48 | return pkg._id + '~' + stage + ':'
49 | }
50 |
51 | function hookStat (dir, stage, cb) {
52 | const hook = path.join(dir, '.hooks', stage)
53 | const cachedStatError = hookStatCache.get(hook)
54 |
55 | if (cachedStatError === undefined) {
56 | return fs.stat(hook, function (statError) {
57 | hookStatCache.set(hook, statError)
58 | cb(statError)
59 | })
60 | }
61 |
62 | return setImmediate(() => cb(cachedStatError))
63 | }
64 |
65 | function lifecycle (pkg, stage, wd, opts) {
66 | return new Promise((resolve, reject) => {
67 | while (pkg && pkg._data) pkg = pkg._data
68 | if (!pkg) return reject(new Error('Invalid package data'))
69 |
70 | opts.log.info('lifecycle', logid(pkg, stage), pkg._id)
71 | if (!pkg.scripts) pkg.scripts = {}
72 |
73 | if (stage === 'prepublish' && opts.ignorePrepublish) {
74 | opts.log.info('lifecycle', logid(pkg, stage), 'ignored because ignore-prepublish is set to true', pkg._id)
75 | delete pkg.scripts.prepublish
76 | }
77 |
78 | hookStat(opts.dir, stage, function (statError) {
79 | // makeEnv is a slow operation. This guard clause prevents makeEnv being called
80 | // and avoids a ton of unnecessary work, and results in a major perf boost.
81 | if (!pkg.scripts[stage] && statError) return resolve()
82 |
83 | validWd(wd || path.resolve(opts.dir, pkg.name), function (er, wd) {
84 | if (er) return reject(er)
85 |
86 | if ((wd.indexOf(opts.dir) !== 0 || _incorrectWorkingDirectory(wd, pkg)) &&
87 | !opts.unsafePerm && pkg.scripts[stage]) {
88 | opts.log.warn('lifecycle', logid(pkg, stage), 'cannot run in wd', pkg._id, pkg.scripts[stage], `(wd=${wd})`)
89 | return resolve()
90 | }
91 |
92 | // set the env variables, then run scripts as a child process.
93 | var env = makeEnv(pkg, opts)
94 | env.npm_lifecycle_event = stage
95 | env.npm_node_execpath = env.NODE = env.NODE || process.execPath
96 | env.npm_execpath = require.main.filename
97 | env.INIT_CWD = process.cwd()
98 | env.npm_config_node_gyp = env.npm_config_node_gyp || DEFAULT_NODE_GYP_PATH
99 |
100 | // 'nobody' typically doesn't have permission to write to /tmp
101 | // even if it's never used, sh freaks out.
102 | if (!opts.unsafePerm) env.TMPDIR = wd
103 |
104 | lifecycle_(pkg, stage, wd, opts, env, (er) => {
105 | if (er) return reject(er)
106 | return resolve()
107 | })
108 | })
109 | })
110 | })
111 | }
112 |
113 | function _incorrectWorkingDirectory (wd, pkg) {
114 | return wd.lastIndexOf(pkg.name) !== wd.length - pkg.name.length
115 | }
116 |
117 | function lifecycle_ (pkg, stage, wd, opts, env, cb) {
118 | var pathArr = []
119 | var p = wd.split(/[\\/]node_modules[\\/]/)
120 | var acc = path.resolve(p.shift())
121 |
122 | p.forEach(function (pp) {
123 | pathArr.unshift(path.join(acc, 'node_modules', '.bin'))
124 | acc = path.join(acc, 'node_modules', pp)
125 | })
126 | pathArr.unshift(path.join(acc, 'node_modules', '.bin'))
127 |
128 | // we also unshift the bundled node-gyp-bin folder so that
129 | // the bundled one will be used for installing things.
130 | pathArr.unshift(path.join(__dirname, 'node-gyp-bin'))
131 |
132 | if (shouldPrependCurrentNodeDirToPATH(opts)) {
133 | // prefer current node interpreter in child scripts
134 | pathArr.push(path.dirname(process.execPath))
135 | }
136 |
137 | const existingPath = mergePath(env)
138 | if (existingPath) pathArr.push(existingPath)
139 | const envPath = pathArr.join(isWindows ? ';' : ':')
140 | setPathEnv(env, envPath)
141 |
142 | var packageLifecycle = pkg.scripts && pkg.scripts.hasOwnProperty(stage)
143 |
144 | if (opts.ignoreScripts) {
145 | opts.log.info('lifecycle', logid(pkg, stage), 'ignored because ignore-scripts is set to true', pkg._id)
146 | packageLifecycle = false
147 | } else if (packageLifecycle) {
148 | // define this here so it's available to all scripts.
149 | env.npm_lifecycle_script = pkg.scripts[stage]
150 | } else {
151 | opts.log.silly('lifecycle', logid(pkg, stage), 'no script for ' + stage + ', continuing')
152 | }
153 |
154 | function done (er) {
155 | if (er) {
156 | if (opts.force) {
157 | opts.log.info('lifecycle', logid(pkg, stage), 'forced, continuing', er)
158 | er = null
159 | } else if (opts.failOk) {
160 | opts.log.warn('lifecycle', logid(pkg, stage), 'continuing anyway', er.message)
161 | er = null
162 | }
163 | }
164 | cb(er)
165 | }
166 |
167 | chain(
168 | [
169 | packageLifecycle && [runPackageLifecycle, pkg, stage, env, wd, opts],
170 | [runHookLifecycle, pkg, stage, env, wd, opts]
171 | ],
172 | done
173 | )
174 | }
175 |
176 | function shouldPrependCurrentNodeDirToPATH (opts) {
177 | const cfgsetting = opts.scriptsPrependNodePath
178 | if (cfgsetting === false) return false
179 | if (cfgsetting === true) return true
180 |
181 | var isDifferentNodeInPath
182 |
183 | var foundExecPath
184 | try {
185 | foundExecPath = which.sync(path.basename(process.execPath), { pathExt: isWindows ? ';' : ':' })
186 | // Apply `fs.realpath()` here to avoid false positives when `node` is a symlinked executable.
187 | isDifferentNodeInPath = fs.realpathSync(process.execPath).toUpperCase() !==
188 | fs.realpathSync(foundExecPath).toUpperCase()
189 | } catch (e) {
190 | isDifferentNodeInPath = true
191 | }
192 |
193 | if (cfgsetting === 'warn-only') {
194 | if (isDifferentNodeInPath && !shouldPrependCurrentNodeDirToPATH.hasWarned) {
195 | if (foundExecPath) {
196 | opts.log.warn('lifecycle', 'The node binary used for scripts is', foundExecPath, 'but npm is using', process.execPath, 'itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.')
197 | } else {
198 | opts.log.warn('lifecycle', 'npm is using', process.execPath, 'but there is no node binary in the current PATH. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.')
199 | }
200 | shouldPrependCurrentNodeDirToPATH.hasWarned = true
201 | }
202 |
203 | return false
204 | }
205 |
206 | return isDifferentNodeInPath
207 | }
208 |
209 | function validWd (d, cb) {
210 | fs.stat(d, function (er, st) {
211 | if (er || !st.isDirectory()) {
212 | var p = path.dirname(d)
213 | if (p === d) {
214 | return cb(new Error('Could not find suitable wd'))
215 | }
216 | return validWd(p, cb)
217 | }
218 | return cb(null, d)
219 | })
220 | }
221 |
222 | function runPackageLifecycle (pkg, stage, env, wd, opts, cb) {
223 | // run package lifecycle scripts in the package root, or the nearest parent.
224 | var cmd = env.npm_lifecycle_script
225 |
226 | var note = '\n> ' + pkg._id + ' ' + stage + ' ' + wd +
227 | '\n> ' + cmd + '\n'
228 | runCmd(note, cmd, pkg, env, stage, wd, opts, cb)
229 | }
230 |
231 | var running = false
232 | var queue = []
233 | function dequeue () {
234 | running = false
235 | if (queue.length) {
236 | var r = queue.shift()
237 | runCmd.apply(null, r)
238 | }
239 | }
240 |
241 | function runCmd (note, cmd, pkg, env, stage, wd, opts, cb) {
242 | if (running) {
243 | queue.push([note, cmd, pkg, env, stage, wd, opts, cb])
244 | return
245 | }
246 |
247 | running = true
248 | opts.log.pause()
249 | var unsafe = opts.unsafePerm
250 | var user = unsafe ? null : opts.user
251 | var group = unsafe ? null : opts.group
252 |
253 | if (opts.log.level !== 'silent') {
254 | opts.log.clearProgress()
255 | console.log(note)
256 | opts.log.showProgress()
257 | }
258 | opts.log.verbose('lifecycle', logid(pkg, stage), 'unsafe-perm in lifecycle', unsafe)
259 |
260 | if (isWindows) {
261 | unsafe = true
262 | }
263 |
264 | if (unsafe) {
265 | runCmd_(cmd, pkg, env, wd, opts, stage, unsafe, 0, 0, cb)
266 | } else {
267 | uidNumber(user, group, function (er, uid, gid) {
268 | if (er) {
269 | er.code = 'EUIDLOOKUP'
270 | opts.log.resume()
271 | process.nextTick(dequeue)
272 | return cb(er)
273 | }
274 | runCmd_(cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb)
275 | })
276 | }
277 | }
278 |
279 | const getSpawnArgs = ({ cmd, wd, opts, uid, gid, unsafe, env }) => {
280 | const conf = {
281 | cwd: wd,
282 | env: env,
283 | stdio: opts.stdio || [ 0, 1, 2 ]
284 | }
285 |
286 | if (!unsafe) {
287 | conf.uid = uid ^ 0
288 | conf.gid = gid ^ 0
289 | }
290 |
291 | const customShell = opts.scriptShell
292 |
293 | let sh = 'sh'
294 | let shFlag = '-c'
295 | if (customShell) {
296 | sh = customShell
297 | } else if (isWindows || opts._TESTING_FAKE_WINDOWS_) {
298 | sh = process.env.comspec || 'cmd'
299 | // '/d /s /c' is used only for cmd.exe.
300 | if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(sh)) {
301 | shFlag = '/d /s /c'
302 | conf.windowsVerbatimArguments = true
303 | }
304 | }
305 |
306 | return [sh, [shFlag, cmd], conf]
307 | }
308 |
309 | exports._getSpawnArgs = getSpawnArgs
310 |
311 | function runCmd_ (cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb_) {
312 | function cb (er) {
313 | cb_.apply(null, arguments)
314 | opts.log.resume()
315 | process.nextTick(dequeue)
316 | }
317 |
318 | const [sh, args, conf] = getSpawnArgs({ cmd, wd, opts, uid, gid, unsafe, env })
319 |
320 | opts.log.verbose('lifecycle', logid(pkg, stage), 'PATH:', env[PATH])
321 | opts.log.verbose('lifecycle', logid(pkg, stage), 'CWD:', wd)
322 | opts.log.silly('lifecycle', logid(pkg, stage), 'Args:', args)
323 |
324 | var proc = spawn(sh, args, conf, opts.log)
325 |
326 | proc.on('error', procError)
327 | proc.on('close', function (code, signal) {
328 | opts.log.silly('lifecycle', logid(pkg, stage), 'Returned: code:', code, ' signal:', signal)
329 | if (signal) {
330 | process.kill(process.pid, signal)
331 | } else if (code) {
332 | var er = new Error('Exit status ' + code)
333 | er.errno = code
334 | }
335 | procError(er)
336 | })
337 | byline(proc.stdout).on('data', function (data) {
338 | opts.log.verbose('lifecycle', logid(pkg, stage), 'stdout', data.toString())
339 | })
340 | byline(proc.stderr).on('data', function (data) {
341 | opts.log.verbose('lifecycle', logid(pkg, stage), 'stderr', data.toString())
342 | })
343 | process.once('SIGTERM', procKill)
344 | process.once('SIGINT', procInterupt)
345 |
346 | function procError (er) {
347 | if (er) {
348 | opts.log.info('lifecycle', logid(pkg, stage), 'Failed to exec ' + stage + ' script')
349 | er.message = pkg._id + ' ' + stage + ': `' + cmd + '`\n' +
350 | er.message
351 | if (er.code !== 'EPERM') {
352 | er.code = 'ELIFECYCLE'
353 | }
354 | fs.stat(opts.dir, function (statError, d) {
355 | if (statError && statError.code === 'ENOENT' && opts.dir.split(path.sep).slice(-1)[0] === 'node_modules') {
356 | opts.log.warn('', 'Local package.json exists, but node_modules missing, did you mean to install?')
357 | }
358 | })
359 | er.pkgid = pkg._id
360 | er.stage = stage
361 | er.script = cmd
362 | er.pkgname = pkg.name
363 | }
364 | process.removeListener('SIGTERM', procKill)
365 | process.removeListener('SIGTERM', procInterupt)
366 | process.removeListener('SIGINT', procKill)
367 | process.removeListener('SIGINT', procInterupt)
368 | return cb(er)
369 | }
370 | function procKill () {
371 | proc.kill()
372 | }
373 | function procInterupt () {
374 | proc.kill('SIGINT')
375 | proc.on('exit', function () {
376 | process.exit()
377 | })
378 | process.once('SIGINT', procKill)
379 | }
380 | }
381 |
382 | function runHookLifecycle (pkg, stage, env, wd, opts, cb) {
383 | hookStat(opts.dir, stage, function (er) {
384 | if (er) return cb()
385 | var cmd = path.join(opts.dir, '.hooks', stage)
386 | var note = '\n> ' + pkg._id + ' ' + stage + ' ' + wd +
387 | '\n> ' + cmd
388 | runCmd(note, cmd, pkg, env, stage, wd, opts, cb)
389 | })
390 | }
391 |
392 | function makeEnv (data, opts, prefix, env) {
393 | prefix = prefix || 'npm_package_'
394 | if (!env) {
395 | env = {}
396 | for (var i in process.env) {
397 | if (!i.match(/^npm_/)) {
398 | env[i] = process.env[i]
399 | }
400 | }
401 |
402 | // express and others respect the NODE_ENV value.
403 | if (opts.production) env.NODE_ENV = 'production'
404 | } else if (!data.hasOwnProperty('_lifecycleEnv')) {
405 | Object.defineProperty(data, '_lifecycleEnv',
406 | {
407 | value: env,
408 | enumerable: false
409 | }
410 | )
411 | }
412 |
413 | if (opts.nodeOptions) env.NODE_OPTIONS = opts.nodeOptions
414 |
415 | for (i in data) {
416 | if (i.charAt(0) !== '_') {
417 | var envKey = (prefix + i).replace(/[^a-zA-Z0-9_]/g, '_')
418 | if (i === 'readme') {
419 | continue
420 | }
421 | if (data[i] && typeof data[i] === 'object') {
422 | try {
423 | // quick and dirty detection for cyclical structures
424 | JSON.stringify(data[i])
425 | makeEnv(data[i], opts, envKey + '_', env)
426 | } catch (ex) {
427 | // usually these are package objects.
428 | // just get the path and basic details.
429 | var d = data[i]
430 | makeEnv(
431 | { name: d.name, version: d.version, path: d.path },
432 | opts,
433 | envKey + '_',
434 | env
435 | )
436 | }
437 | } else {
438 | env[envKey] = String(data[i])
439 | env[envKey] = env[envKey].indexOf('\n') !== -1
440 | ? JSON.stringify(env[envKey])
441 | : env[envKey]
442 | }
443 | }
444 | }
445 |
446 | if (prefix !== 'npm_package_') return env
447 |
448 | prefix = 'npm_config_'
449 | var pkgConfig = {}
450 | var pkgVerConfig = {}
451 | var namePref = data.name + ':'
452 | var verPref = data.name + '@' + data.version + ':'
453 |
454 | Object.keys(opts.config).forEach(function (i) {
455 | // in some rare cases (e.g. working with nerf darts), there are segmented
456 | // "private" (underscore-prefixed) config names -- don't export
457 | if ((i.charAt(0) === '_' && i.indexOf('_' + namePref) !== 0) || i.match(/:_/)) {
458 | return
459 | }
460 | var value = opts.config[i]
461 | if (value instanceof Stream || Array.isArray(value) || typeof value === 'function') return
462 | if (i.match(/umask/)) value = umask.toString(value)
463 |
464 | if (!value) value = ''
465 | else if (typeof value === 'number') value = '' + value
466 | else if (typeof value !== 'string') value = JSON.stringify(value)
467 |
468 | if (typeof value !== 'string') {
469 | return
470 | }
471 |
472 | value = value.indexOf('\n') !== -1
473 | ? JSON.stringify(value)
474 | : value
475 | i = i.replace(/^_+/, '')
476 | var k
477 | if (i.indexOf(namePref) === 0) {
478 | k = i.substr(namePref.length).replace(/[^a-zA-Z0-9_]/g, '_')
479 | pkgConfig[k] = value
480 | } else if (i.indexOf(verPref) === 0) {
481 | k = i.substr(verPref.length).replace(/[^a-zA-Z0-9_]/g, '_')
482 | pkgVerConfig[k] = value
483 | }
484 | var envKey = (prefix + i).replace(/[^a-zA-Z0-9_]/g, '_')
485 | env[envKey] = value
486 | })
487 |
488 | prefix = 'npm_package_config_'
489 | ;[pkgConfig, pkgVerConfig].forEach(function (conf) {
490 | for (var i in conf) {
491 | var envKey = (prefix + i)
492 | env[envKey] = conf[i]
493 | }
494 | })
495 |
496 | return env
497 | }
498 |
--------------------------------------------------------------------------------
/lib/spawn.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = spawn
4 |
5 | const _spawn = require('child_process').spawn
6 | const EventEmitter = require('events').EventEmitter
7 |
8 | let progressEnabled
9 | let running = 0
10 |
11 | function startRunning (log) {
12 | if (progressEnabled == null) progressEnabled = log.progressEnabled
13 | if (progressEnabled) log.disableProgress()
14 | ++running
15 | }
16 |
17 | function stopRunning (log) {
18 | --running
19 | if (progressEnabled && running === 0) log.enableProgress()
20 | }
21 |
22 | function willCmdOutput (stdio) {
23 | if (stdio === 'inherit') return true
24 | if (!Array.isArray(stdio)) return false
25 | for (let fh = 1; fh <= 2; ++fh) {
26 | if (stdio[fh] === 'inherit') return true
27 | if (stdio[fh] === 1 || stdio[fh] === 2) return true
28 | }
29 | return false
30 | }
31 |
32 | function spawn (cmd, args, options, log) {
33 | const cmdWillOutput = willCmdOutput(options && options.stdio)
34 |
35 | if (cmdWillOutput) startRunning(log)
36 | const raw = _spawn(cmd, args, options)
37 | const cooked = new EventEmitter()
38 |
39 | raw.on('error', function (er) {
40 | if (cmdWillOutput) stopRunning(log)
41 | er.file = cmd
42 | cooked.emit('error', er)
43 | }).on('close', function (code, signal) {
44 | if (cmdWillOutput) stopRunning(log)
45 | // Create ENOENT error because Node.js v8.0 will not emit
46 | // an `error` event if the command could not be found.
47 | if (code === 127) {
48 | const er = new Error('spawn ENOENT')
49 | er.code = 'ENOENT'
50 | er.errno = 'ENOENT'
51 | er.syscall = 'spawn'
52 | er.file = cmd
53 | cooked.emit('error', er)
54 | } else {
55 | cooked.emit('close', code, signal)
56 | }
57 | })
58 |
59 | cooked.stdin = raw.stdin
60 | cooked.stdout = raw.stdout
61 | cooked.stderr = raw.stderr
62 | cooked.kill = function (sig) { return raw.kill(sig) }
63 |
64 | return cooked
65 | }
66 |
--------------------------------------------------------------------------------
/node-gyp-bin/node-gyp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | if [ "x$npm_config_node_gyp" = "x" ]; then
3 | node "`dirname "$0"`/../../node_modules/node-gyp/bin/node-gyp.js" "$@"
4 | else
5 | "$npm_config_node_gyp" "$@"
6 | fi
7 |
--------------------------------------------------------------------------------
/node-gyp-bin/node-gyp.cmd:
--------------------------------------------------------------------------------
1 | if not defined npm_config_node_gyp (
2 | node "%~dp0\..\..\node_modules\node-gyp\bin\node-gyp.js" %*
3 | ) else (
4 | node "%npm_config_node_gyp%" %*
5 | )
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npm-lifecycle",
3 | "version": "3.1.5",
4 | "description": "JavaScript package lifecycle hook runner",
5 | "main": "index.js",
6 | "scripts": {
7 | "prerelease": "npm t",
8 | "postrelease": "npm publish && git push --follow-tags",
9 | "pretest": "standard",
10 | "release": "standard-version -s",
11 | "test": "tap -J --cov test/*.js",
12 | "snap": "TAP_SNAPSHOT=1 npm test"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git://github.com/npm/lifecycle.git"
17 | },
18 | "keywords": [
19 | "npm",
20 | "lifecycle",
21 | "hook",
22 | "runner"
23 | ],
24 | "author": "Mike Sherov",
25 | "license": "Artistic-2.0",
26 | "bugs": {
27 | "url": "https://github.com/npm/lifecycle/issues"
28 | },
29 | "homepage": "https://github.com/npm/lifecycle#readme",
30 | "dependencies": {
31 | "byline": "^5.0.0",
32 | "graceful-fs": "^4.1.15",
33 | "node-gyp": "^5.0.2",
34 | "resolve-from": "^4.0.0",
35 | "slide": "^1.1.6",
36 | "uid-number": "0.0.6",
37 | "umask": "^1.1.0",
38 | "which": "^1.3.1"
39 | },
40 | "devDependencies": {
41 | "nyc": "^14.1.0",
42 | "sinon": "^7.2.3",
43 | "standard": "^12.0.1",
44 | "standard-version": "^4.4.0",
45 | "tap": "^12.7.0"
46 | },
47 | "files": [
48 | "index.js",
49 | "lib/spawn.js",
50 | "node-gyp-bin"
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/tap-snapshots/test-get-spawn-args.js-TAP.test.js:
--------------------------------------------------------------------------------
1 | /* IMPORTANT
2 | * This snapshot file is auto-generated, but designed for humans.
3 | * It should be checked into source control and tracked carefully.
4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5 | * Make sure to inspect the output below. Do not ignore changes!
6 | */
7 | 'use strict'
8 | exports[`test/get-spawn-args.js TAP > custom windows script shell 1`] = `
9 | [
10 | "flerbbyderb",
11 | [
12 | "-c",
13 | "cmd"
14 | ],
15 | {
16 | "cwd": "/working/dir",
17 | "env": {
18 | "env": "iron",
19 | "men": "tal"
20 | },
21 | "stdio": [
22 | 0,
23 | 1,
24 | 2
25 | ],
26 | "uid": 123,
27 | "gid": 432
28 | }
29 | ]
30 | `
31 |
32 | exports[`test/get-spawn-args.js TAP > just basics 1`] = `
33 | [
34 | "sh",
35 | [
36 | "-c",
37 | "cmd"
38 | ],
39 | {
40 | "cwd": "/working/dir",
41 | "env": {
42 | "env": "iron",
43 | "men": "tal"
44 | },
45 | "stdio": [
46 | 0,
47 | 1,
48 | 2
49 | ],
50 | "uid": 123,
51 | "gid": 432
52 | }
53 | ]
54 | `
55 |
56 | exports[`test/get-spawn-args.js TAP > stdio and numeric string uid 1`] = `
57 | [
58 | "sh",
59 | [
60 | "-c",
61 | "cmd"
62 | ],
63 | {
64 | "cwd": "/working/dir",
65 | "env": {
66 | "env": "iron",
67 | "men": "tal"
68 | },
69 | "stdio": [
70 | 3,
71 | 2,
72 | 1
73 | ],
74 | "uid": 123,
75 | "gid": 432
76 | }
77 | ]
78 | `
79 |
80 | exports[`test/get-spawn-args.js TAP > unsafe numeric string uid 1`] = `
81 | [
82 | "sh",
83 | [
84 | "-c",
85 | "cmd"
86 | ],
87 | {
88 | "cwd": "/working/dir",
89 | "env": {
90 | "env": "iron",
91 | "men": "tal"
92 | },
93 | "stdio": [
94 | 3,
95 | 2,
96 | 1
97 | ]
98 | }
99 | ]
100 | `
101 |
102 | exports[`test/get-spawn-args.js TAP > weird comspec 1`] = `
103 | [
104 | "flerbbyderb",
105 | [
106 | "-c",
107 | "cmd"
108 | ],
109 | {
110 | "cwd": "/working/dir",
111 | "env": {
112 | "env": "iron",
113 | "men": "tal"
114 | },
115 | "stdio": [
116 | 0,
117 | 1,
118 | 2
119 | ],
120 | "uid": 123,
121 | "gid": 432
122 | }
123 | ]
124 | `
125 |
126 | exports[`test/get-spawn-args.js TAP > windows 1`] = `
127 | [
128 | "CMD.exe",
129 | [
130 | "/d /s /c",
131 | "cmd"
132 | ],
133 | {
134 | "cwd": "/working/dir",
135 | "env": {
136 | "env": "iron",
137 | "men": "tal"
138 | },
139 | "stdio": [
140 | 0,
141 | 1,
142 | 2
143 | ],
144 | "uid": 123,
145 | "gid": 432,
146 | "windowsVerbatimArguments": true
147 | }
148 | ]
149 | `
150 |
--------------------------------------------------------------------------------
/tap-snapshots/test-path-munging.js-TAP.test.js:
--------------------------------------------------------------------------------
1 | /* IMPORTANT
2 | * This snapshot file is auto-generated, but designed for humans.
3 | * It should be checked into source control and tracked carefully.
4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5 | * Make sure to inspect the output below. Do not ignore changes!
6 | */
7 | 'use strict'
8 | exports[`test/path-munging.js TAP mergePath > merge all paths together 1`] = `
9 | "a:b:c:d:x:y:z"
10 | `
11 |
12 | exports[`test/path-munging.js TAP setPathEnv > set all path envs 1`] = `
13 | {
14 | "Path": "a:b:c",
15 | "PATH": "a:b:c",
16 | "pAtH": "a:b:c",
17 | "a": "b"
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/test/fixtures/count-to-10-working-postinstall/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "count-to-10-working-postinstall",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "postinstall": "node postinstall.js"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/count-to-10-working-postinstall/postinstall.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | console.log('line 1')
4 | console.log('line 2')
5 | console.error('some error')
6 |
--------------------------------------------------------------------------------
/test/fixtures/count-to-10/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "count-to-10",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "postinstall": "scripts/postinstall.js"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/count-to-10/postinstall.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | console.log('line 1')
4 | console.log('line 2')
5 | console.error('some error')
6 |
--------------------------------------------------------------------------------
/test/fixtures/has-hooks/node_modules/.hooks/postinstall:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | console.log('ran hook');
--------------------------------------------------------------------------------
/test/fixtures/has-hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "has-hooks",
3 | "version": "1.0.0"
4 | }
5 |
--------------------------------------------------------------------------------
/test/get-spawn-args.js:
--------------------------------------------------------------------------------
1 | const t = require('tap')
2 | const getSpawnArgs = require('../')._getSpawnArgs
3 |
4 | // these show up in the result, but aren't forked on
5 | // just set the same for every case.
6 | const cmd = 'cmd'
7 | const wd = '/working/dir'
8 | const env = { env: 'iron', men: 'tal' }
9 | const b = { cmd, wd, env, uid: 123, gid: 432, opts: {} }
10 |
11 | const snap = (o, m) => t.matchSnapshot(JSON.stringify(o, null, 2), m)
12 |
13 | snap(getSpawnArgs(b), 'just basics')
14 |
15 | snap(getSpawnArgs(Object.assign({}, b, {
16 | opts: { stdio: [3, 2, 1] },
17 | uid: '123'
18 | })), 'stdio and numeric string uid')
19 |
20 | snap(getSpawnArgs(Object.assign({}, b, {
21 | opts: { stdio: [3, 2, 1] },
22 | uid: '123',
23 | unsafe: true
24 | })), 'unsafe numeric string uid')
25 |
26 | process.env.comspec = 'CMD.exe'
27 | snap(getSpawnArgs(Object.assign({}, b, {
28 | opts: {
29 | _TESTING_FAKE_WINDOWS_: true
30 | }
31 | })), 'windows')
32 |
33 | snap(getSpawnArgs(Object.assign({}, b, {
34 | opts: {
35 | _TESTING_FAKE_WINDOWS_: true,
36 | scriptShell: 'flerbbyderb'
37 | }
38 | })), 'custom windows script shell')
39 |
40 | process.env.comspec = 'flerbbyderb'
41 | snap(getSpawnArgs(Object.assign({}, b, {
42 | opts: {
43 | _TESTING_FAKE_WINDOWS_: true
44 | }
45 | })), 'weird comspec')
46 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tap').test
4 | const sinon = require('sinon')
5 | const lifecycle = require('../index.js')
6 | const path = require('path')
7 |
8 | function noop () {}
9 |
10 | test('makeEnv', function (t) {
11 | const pkg = {
12 | name: 'myPackage',
13 | version: '1.0.0',
14 | contributors: [{ name: 'Mike Sherov', email: 'beep@boop.com' }]
15 | }
16 | const config = {
17 | enteente: Infinity,
18 | '_privateVar': 1,
19 | '_myPackage:myPrivateVar': 1,
20 | 'myPackage:bar': 2,
21 | 'myPackage:foo': 3,
22 | 'myPackage@1.0.0:baz': 4,
23 | 'myPackage@1.0.0:foo': 5,
24 | ignoreThisFunction: () => {},
25 | jsonsAsUndefined: { toJSON: () => undefined },
26 | jsonsAsFunction: { toJSON: () => () => {} }
27 | }
28 |
29 | const env = lifecycle.makeEnv(pkg, {
30 | config,
31 | nodeOptions: '--inspect-brk --abort-on-uncaught-exception'
32 | }, null, Object.assign({}, process.env))
33 |
34 | t.equal('myPackage', env.npm_package_name, 'package data is included')
35 | t.equal('Mike Sherov', env.npm_package_contributors_0_name, 'nested package data is included')
36 |
37 | t.equal('Infinity', env.npm_config_enteente, 'public config is included')
38 | t.equal(undefined, env.npm_config_privateVar, 'private config is excluded')
39 |
40 | t.equal('1', env.npm_config_myPackage_myPrivateVar, 'private package config is included by name')
41 | t.equal('2', env.npm_config_myPackage_bar, 'public package config is included by name')
42 |
43 | t.equal('5', env.npm_config_myPackage_1_0_0_foo, 'public package@version config is included by name')
44 |
45 | t.equal('1', env.npm_package_config_myPrivateVar, 'package private config is included')
46 | t.equal('2', env.npm_package_config_bar, 'package config is included')
47 | t.equal('4', env.npm_package_config_baz, 'package@version config is included')
48 | t.equal('5', env.npm_package_config_foo, 'package@version config overrides package config')
49 |
50 | t.equal(env.npm_package_config_ignoreThisFunction, undefined)
51 | t.equal(env.npm_package_config_jsonsAsUndefined, undefined)
52 | t.equal(env.npm_package_config_jsonsAsFunction, undefined)
53 |
54 | t.equal('--inspect-brk --abort-on-uncaught-exception', env.NODE_OPTIONS, 'nodeOptions sets NODE_OPTIONS')
55 | t.end()
56 | })
57 |
58 | test('_incorrectWorkingDirectory: accepts wd for package that matches project\'s name', function (t) {
59 | const wd = '/opt/my-time/node_modules/time'
60 | const pkg = { name: 'time' }
61 |
62 | t.equal(lifecycle._incorrectWorkingDirectory(wd, pkg), false)
63 | t.end()
64 | })
65 |
66 | test('_incorrectWorkingDirectory: accepts wd for package that doesn\'t match project\'s name', function (t) {
67 | const wd = '/opt/my-project/node_modules/time'
68 | const pkg = { name: 'time' }
69 |
70 | t.equal(lifecycle._incorrectWorkingDirectory(wd, pkg), false)
71 | t.end()
72 | })
73 |
74 | test('_incorrectWorkingDirectory: rejects wd from other packages', function (t) {
75 | const wd = '/opt/my-time/node_modules/time/invalid'
76 | const pkg = {
77 | name: 'time'
78 | }
79 |
80 | t.equal(lifecycle._incorrectWorkingDirectory(wd, pkg), true)
81 | t.end()
82 | })
83 |
84 | test('runs scripts from .hooks directory even if no script is present in package.json', function (t) {
85 | const fixture = path.join(__dirname, 'fixtures', 'has-hooks')
86 |
87 | const verbose = sinon.spy()
88 | const silly = sinon.spy()
89 | const log = {
90 | level: 'silent',
91 | info: noop,
92 | warn: noop,
93 | silly,
94 | verbose,
95 | pause: noop,
96 | resume: noop,
97 | clearProgress: noop,
98 | showProgress: noop
99 | }
100 | const dir = path.join(fixture, 'node_modules')
101 |
102 | const pkg = require(path.join(fixture, 'package.json'))
103 |
104 | lifecycle(pkg, 'postinstall', fixture, {
105 | stdio: 'pipe',
106 | log,
107 | dir,
108 | config: {}
109 | })
110 | .then(() => {
111 | t.ok(
112 | verbose.calledWithMatch(
113 | 'lifecycle',
114 | 'undefined~postinstall:',
115 | 'stdout',
116 | 'ran hook'
117 | ),
118 | 'ran postinstall hook'
119 | )
120 |
121 | t.end()
122 | })
123 | .catch(t.end)
124 | })
125 |
126 | test("reports child's output", function (t) {
127 | const fixture = path.join(__dirname, 'fixtures', 'count-to-10')
128 |
129 | const verbose = sinon.spy()
130 | const silly = sinon.spy()
131 | const log = {
132 | level: 'silent',
133 | info: noop,
134 | warn: noop,
135 | silly,
136 | verbose,
137 | pause: noop,
138 | resume: noop,
139 | clearProgress: noop,
140 | showProgress: noop
141 | }
142 | const dir = path.join(__dirname, '..')
143 |
144 | const pkg = require(path.join(fixture, 'package.json'))
145 |
146 | lifecycle(pkg, 'postinstall', fixture, {
147 | stdio: 'pipe',
148 | log,
149 | dir,
150 | config: {}
151 | })
152 | .then(() => {
153 | t.ok(
154 | verbose.calledWithMatch(
155 | 'lifecycle',
156 | 'undefined~postinstall:',
157 | 'stdout',
158 | 'line 1'
159 | ),
160 | 'stdout reported'
161 | )
162 | t.ok(
163 | verbose.calledWithMatch(
164 | 'lifecycle',
165 | 'undefined~postinstall:',
166 | 'stdout',
167 | 'line 2'
168 | ),
169 | 'stdout reported'
170 | )
171 | t.ok(
172 | verbose.calledWithMatch(
173 | 'lifecycle',
174 | 'undefined~postinstall:',
175 | 'stderr',
176 | 'some error'
177 | ),
178 | 'stderr reported'
179 | )
180 | t.ok(
181 | silly.calledWithMatch(
182 | 'lifecycle',
183 | 'undefined~postinstall:',
184 | 'Returned: code:',
185 | 0,
186 | ' signal:',
187 | null
188 | ),
189 | 'exit code reported'
190 | )
191 |
192 | t.end()
193 | })
194 | .catch(t.end)
195 | })
196 |
197 | test('fails when given an invalid user id', {
198 | skip: process.platform === 'win32' ? 'windows always in unsafe mode' : false
199 | }, function (t) {
200 | const fixture = path.join(__dirname, 'fixtures', 'count-to-10-working-postinstall')
201 | const dir = fixture
202 |
203 | const pkg = require(path.join(fixture, 'package.json'))
204 | t.rejects(lifecycle(pkg, 'postinstall', fixture, {
205 | stdio: 'pipe',
206 | log: {
207 | level: 'silent',
208 | info: noop,
209 | warn: noop,
210 | silly: noop,
211 | verbose: noop,
212 | pause: noop,
213 | resume: noop,
214 | clearProgress: noop,
215 | showProgress: noop
216 | },
217 | dir,
218 | user: 'this-user-name-does-not-exist-at-least-i-sure-hope-not',
219 | group: 'maybe-there-are-a-bunch-of-us-who-do-not-exist',
220 | unsafePerm: false,
221 | config: {}
222 | }), { code: 'EUIDLOOKUP' })
223 | t.end()
224 | })
225 |
--------------------------------------------------------------------------------
/test/path-munging.js:
--------------------------------------------------------------------------------
1 | require('path').delimiter = ':'
2 | const { _setPathEnv, _mergePath } = require('../')
3 | const t = require('tap')
4 |
5 | const snap = (t, o, m) => t.matchSnapshot(JSON.stringify(o, 0, 2), m)
6 |
7 | t.test('setPathEnv', t => {
8 | const e = {
9 | Path: 1,
10 | PATH: 2,
11 | pAtH: 3,
12 | a: 'b'
13 | }
14 | _setPathEnv(e, 'a:b:c')
15 | snap(t, e, 'set all path envs')
16 | t.end()
17 | })
18 |
19 | t.test('mergePath', t => {
20 | const e = {
21 | Path: 'a:b:c',
22 | PATH: 'a:b:c:d',
23 | PaTh: 'd:c:x:y:z',
24 | pAtH: ''
25 | }
26 | snap(t, _mergePath(e), 'merge all paths together')
27 | t.end()
28 | })
29 |
--------------------------------------------------------------------------------