Skip to content

Commit 0642472

Browse files
committed
fix: handle edge case and update README for clarity
1 parent 3ff5b3d commit 0642472

File tree

6 files changed

+122
-33
lines changed

6 files changed

+122
-33
lines changed

README.md

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
1-
# cross-env 🔀
1+
<div align="center">
2+
<h1>cross-env 🔀</h1>
23

3-
Run scripts that set and use environment variables across platforms.
4+
<p>Run scripts that set and use environment variables across platforms</p>
5+
</div>
46

5-
## The Problem
7+
**🎉 NOTICE: cross-env is "done" as in it does what it does and there's no need
8+
for new features.
9+
[Learn more](https://github.com/kentcdodds/cross-env/issues/257)**
10+
11+
---
12+
13+
<!-- prettier-ignore-start -->
14+
[![Build Status][build-badge]][build]
15+
[![Code Coverage][coverage-badge]][coverage]
16+
[![version][version-badge]][package]
17+
[![downloads][downloads-badge]][npmtrends]
18+
[![MIT License][license-badge]][license]
19+
<!-- prettier-ignore-end -->
20+
21+
## The problem
622

723
Most Windows command prompts will choke when you set environment variables with
8-
`NODE_ENV=production` like that. Similarly, there's a difference in how Windows
9-
and POSIX commands utilize environment variables. With POSIX, you use:
10-
`$ENV_VAR` and on Windows you use `%ENV_VAR%`.
24+
`NODE_ENV=production` like that. (The exception is [Bash on Windows][win-bash],
25+
which uses native Bash.) Similarly, there's a difference in how windows and
26+
POSIX commands utilize environment variables. With POSIX, you use: `$ENV_VAR`
27+
and on windows you use `%ENV_VAR%`.
1128

12-
## The Solution
29+
## This solution
1330

1431
`cross-env` makes it so you can have a single command without worrying about
1532
setting or using the environment variable properly for the platform. Just set it
@@ -18,18 +35,27 @@ of setting it properly.
1835

1936
## Installation
2037

21-
```bash
38+
This module is distributed via [npm][npm] which is bundled with [node][node] and
39+
should be installed as one of your project's `devDependencies`:
40+
41+
```
2242
npm install --save-dev cross-env
2343
```
2444

45+
> WARNING! Make sure that when you're installing packages that you spell things
46+
> correctly to avoid [mistakenly installing malware][malware]
47+
48+
> NOTE : Version 8 of cross-env only supports Node.js 20 and higher, to use it
49+
> on Node.js 18 or lower install version 7 `npm install --save-dev cross-env@7`
50+
2551
## Usage
2652

2753
I use this in my npm scripts:
2854

2955
```json
3056
{
3157
"scripts": {
32-
"build": "cross-env NODE_ENV=production node ./start.js"
58+
"build": "cross-env NODE_ENV=production node ./start.js --enable-turbo-mode"
3359
}
3460
}
3561
```
@@ -38,12 +64,10 @@ Ultimately, the command that is executed (using [`cross-spawn`][cross-spawn])
3864
is:
3965

4066
```
41-
node ./start.js
67+
node ./start.js --enable-turbo-mode
4268
```
4369

44-
The `NODE_ENV` environment variable will be set by `cross-env`.
45-
46-
### Multiple Environment Variables
70+
The `NODE_ENV` environment variable will be set by `cross-env`
4771

4872
You can set multiple environment variables at a time:
4973

@@ -55,10 +79,8 @@ You can set multiple environment variables at a time:
5579
}
5680
```
5781

58-
### Complex Commands
59-
6082
You can also split a command into several ones, or separate the environment
61-
variables declaration from the actual command execution:
83+
variables declaration from the actual command execution. You can do it this way:
6284

6385
```json
6486
{
@@ -69,9 +91,20 @@ variables declaration from the actual command execution:
6991
}
7092
```
7193

72-
### JSON Strings
94+
Where `childScript` holds the actual command to execute and `parentScript` sets
95+
the environment variables to use. Then instead of run the childScript you run
96+
the parent. This is quite useful for launching the same command with different
97+
env variables or when the environment variables are too long to have everything
98+
in one line. It also means that you can use `$GREET` env var syntax even on
99+
Windows which would usually require it to be `%GREET%`.
100+
101+
If you precede a dollar sign with an odd number of backslashes the expression
102+
statement will not be replaced. Note that this means backslashes after the JSON
103+
string escaping took place. `"FOO=\\$BAR"` will not be replaced.
104+
`"FOO=\\\\$BAR"` will be replaced though.
73105

74-
For JSON strings (e.g., when using [ts-loader]):
106+
Lastly, if you want to pass a JSON string (e.g., when using [ts-loader]), you
107+
can do as follows:
75108

76109
```json
77110
{
@@ -82,17 +115,21 @@ For JSON strings (e.g., when using [ts-loader]):
82115
```
83116

84117
Pay special attention to the **triple backslash** `(\\\)` **before** the
85-
**double quotes** `(")` and the **absence** of **single quotes** `(')`.
118+
**double quotes** `(")` and the **absence** of **single quotes** `(')`. Both of
119+
these conditions have to be met in order to work both on Windows and UNIX.
86120

87121
## `cross-env` vs `cross-env-shell`
88122

89-
The `cross-env` module exposes two bins: `cross-env` and `cross-env-shell`.
123+
The `cross-env` module exposes two bins: `cross-env` and `cross-env-shell`. The
124+
first one executes commands using [`cross-spawn`][cross-spawn], while the second
125+
one uses the `shell` option from Node's `spawn`.
90126

91-
- **`cross-env`**: Executes commands using [`cross-spawn`][cross-spawn]
92-
- **`cross-env-shell`**: Uses the `shell` option from Node's `spawn`
127+
The main use case for `cross-env-shell` is when you need an environment variable
128+
to be set across an entire inline shell script, rather than just one command.
93129

94-
Use `cross-env-shell` when you need an environment variable to be set across an
95-
entire inline shell script, rather than just one command:
130+
For example, if you want to have the environment variable apply to several
131+
commands in series then you will need to wrap those in quotes and use
132+
`cross-env-shell` instead of `cross-env`.
96133

97134
```json
98135
{
@@ -102,25 +139,56 @@ entire inline shell script, rather than just one command:
102139
}
103140
```
104141

105-
**Rule of thumb**: If you want to pass to `cross-env` a command that contains
142+
The rule of thumb is: if you want to pass to `cross-env` a command that contains
106143
special shell characters _that you want interpreted_, then use
107144
`cross-env-shell`. Otherwise stick to `cross-env`.
108145

146+
On Windows you need to use `cross-env-shell`, if you want to handle
147+
[signal events](https://nodejs.org/api/process.html#process_signal_events)
148+
inside of your program. A common case for that is when you want to capture a
149+
`SIGINT` event invoked by pressing `Ctrl + C` on the command-line interface.
150+
109151
## Windows Issues
110152

111153
Please note that `npm` uses `cmd` by default and that doesn't support command
112154
substitution, so if you want to leverage that, then you need to update your
113155
`.npmrc` to set the `script-shell` to powershell.
156+
[Learn more here](https://github.com/kentcdodds/cross-env/issues/192#issuecomment-513341729).
157+
158+
## Inspiration
159+
160+
I originally created this to solve a problem I was having with my npm scripts in
161+
[angular-formly][angular-formly]. This made contributing to the project much
162+
easier for Windows users.
114163

115-
## Requirements
164+
## Other Solutions
116165

117-
- Node.js >= 20
166+
- [`env-cmd`](https://github.com/toddbluhm/env-cmd) - Reads environment
167+
variables from a file instead
168+
- [`@naholyr/cross-env`](https://www.npmjs.com/package/@naholyr/cross-env) -
169+
`cross-env` with support for setting default values
118170

119-
## License
171+
## LICENSE
120172

121173
MIT
122174

123175
<!-- prettier-ignore-start -->
176+
[npm]: https://npmjs.com
177+
[node]: https://nodejs.org
178+
[build-badge]: https://img.shields.io/github/actions/workflow/status/kentcdodds/cross-env/validate.yml?branch=main&logo=github&style=flat-square
179+
[build]: https://github.com/kentcdodds/cross-env/actions?query=workflow%3Avalidate
180+
[coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/cross-env.svg?style=flat-square
181+
[coverage]: https://codecov.io/github/kentcdodds/cross-env
182+
[version-badge]: https://img.shields.io/npm/v/cross-env.svg?style=flat-square
183+
[package]: https://www.npmjs.com/package/cross-env
184+
[downloads-badge]: https://img.shields.io/npm/dm/cross-env.svg?style=flat-square
185+
[npmtrends]: http://www.npmtrends.com/cross-env
186+
[license-badge]: https://img.shields.io/npm/l/cross-env.svg?style=flat-square
187+
[license]: https://github.com/kentcdodds/cross-env/blob/master/LICENSE
188+
189+
[angular-formly]: https://github.com/formly-js/angular-formly
124190
[cross-spawn]: https://www.npmjs.com/package/cross-spawn
191+
[malware]: http://blog.npmjs.org/post/163723642530/crossenv-malware-on-the-npm-registry
125192
[ts-loader]: https://www.npmjs.com/package/ts-loader
193+
[win-bash]: https://msdn.microsoft.com/en-us/commandline/wsl/about
126194
<!-- prettier-ignore-end -->

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,19 @@
4141
"author": "Kent C. Dodds <[email protected]> (https://kentcdodds.com)",
4242
"license": "MIT",
4343
"dependencies": {
44+
"@epic-web/invariant": "^1.0.0",
4445
"cross-spawn": "^7.0.6"
4546
},
4647
"devDependencies": {
4748
"@epic-web/config": "^1.21.1",
48-
"eslint": "^9.32.0",
49-
"@types/node": "^24.1.0",
5049
"@types/cross-spawn": "^6.0.6",
50+
"@types/node": "^24.1.0",
51+
"@vitest/coverage-v8": "^3.2.4",
52+
"@vitest/ui": "^3.2.4",
53+
"eslint": "^9.32.0",
5154
"prettier": "^3.6.2",
5255
"typescript": "^5.8.3",
5356
"vitest": "^3.2.4",
54-
"@vitest/ui": "^3.2.4",
55-
"@vitest/coverage-v8": "^3.2.4",
5657
"zshy": "^0.2.5"
5758
},
5859
"repository": {

src/__tests__/index.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ describe('crossEnv', () => {
120120
expect(crossSpawnMock.spawn).toHaveBeenCalledTimes(0)
121121
})
122122

123+
test('handles empty command after processing', () => {
124+
crossEnv(['FOO=bar', ''])
125+
expect(crossSpawnMock.spawn).toHaveBeenCalledTimes(0)
126+
})
127+
123128
test('normalizes commands on windows', () => {
124129
isWindowsMock.mockReturnValue(true)
125130
crossEnv(['./cmd.bat'])

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type SpawnOptions } from 'child_process'
2+
import { invariant } from '@epic-web/invariant'
23
import { spawn } from 'cross-spawn'
34
import { commandConvert } from './command.js'
45
import { varValueConvert } from './variable.js'
@@ -96,7 +97,9 @@ function parseCommand(
9697
return ''
9798
})
9899
})
99-
command = cStart[0] || null
100+
const parsedCommand = cStart[0]
101+
invariant(parsedCommand, 'Command is required')
102+
command = parsedCommand
100103
commandArgs = cStart.slice(1).filter(Boolean)
101104
break
102105
}

vitest.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ export default defineConfig({
77
coverage: {
88
provider: 'v8',
99
reporter: ['text', 'json', 'html'],
10+
include: ['src/**/*'],
1011
exclude: [
1112
'node_modules/',
1213
'dist/',
1314
'**/*.d.ts',
1415
'**/*.config.*',
1516
'**/coverage/**',
17+
'**/*.test.ts',
18+
'**/*.spec.ts',
19+
'**/src/bin/**', // covered by e2e
20+
'**/__tests__/**',
1621
],
1722
},
1823
},

0 commit comments

Comments
 (0)