Ed Graham
Ed Graham

Reputation: 4685

Cross-platform way to set environment variables *with spaces* in npm scripts

I am developing a serverless NodeJS app and need to test it in offline mode. I have an npm script in a package.json file that looks like this:

  "scripts": {
    "serve": "cross-env AUTHORIZER='{\\\"claims\\\":{\\\"permissions\\\":\\\"[view:accounts manage:accounts]\\\",\\\"sub\\\":\\\"auth0|5cfe0adce3c4c50ea072ea9f\\\"}}' AWS_PROFILE=elit_nonprd serverless offline start -s dev --noAuth",
...

Note that there two permissions that need to be separated by a space. Running npm run serve on Windows gives the following error:

> @[email protected] serve C:\path
> cross-env AUTHORIZER='{\"claims\":{\"permissions\":\"[view:accounts manage:accounts]\",\"sub\":\"auth0|5cfe0adce3c4c50ea072ea9f\"}}' AWS_PROFILE=elit_nonprd serverless offline start -s dev --noAuth

The filename, directory name, or volume label syntax is incorrect.
events.js:288
      throw er; // Unhandled 'error' event
      ^

Error: spawn manage:accounts]","sub":"auth0|5cfe0adce3c4c50ea072ea9f"}} ENOENT
    at notFoundError (C:\path\node_modules\cross-spawn\lib\enoent.js:6:26)
    at verifyENOENT (C:\path\node_modules\cross-spawn\lib\enoent.js:40:16)
    at ChildProcess.cp.emit (C:\path\node_modules\cross-spawn\lib\enoent.js:27:25)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12)
Emitted 'error' event on ChildProcess instance at:
    at ChildProcess.cp.emit (C:\path\node_modules\cross-spawn\lib\enoent.js:30:37)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12) {
  code: 'ENOENT',
  errno: 'ENOENT',
  syscall: 'spawn manage:accounts]","sub":"auth0|5cfe0adce3c4c50ea072ea9f"}}',
  path: 'manage:accounts]","sub":"auth0|5cfe0adce3c4c50ea072ea9f"}}',
  spawnargs: [
    'AWS_PROFILE=elit_nonprd',
    'serverless',
    'offline',
    'start',
    '-s',
    'dev',
    '--noAuth'
  ]
}

This also happens if I replace cross-env with cross-env-shell, although the stack-trace is not shown.

Is there a common cross-platform way to set environment variables when the values contain spaces?

Update: the outcome I am hoping for is to set AUTHORIZER to the following value (thanks to @RobC for requesting clarification):

{
  "claims":
  {
    "permissions": "[view:accounts manage:accounts]",
    "sub": "auth0|5cfe0adce3c4c50ea072ea9f"
  }
}

Upvotes: 2

Views: 3120

Answers (1)

RobC
RobC

Reputation: 24952

What a puzzle this is to achieve cross-platform compatibility. After several failed attempts it does not seem possible to utilize a single syntax with cross-env:

Windows (cmd) works successfully with the worksOnWin example below, and *Nix (sh) works successfully with the worksOnNix example below.

{
  "scripts": {
    "worksOnWin": "cross-env AUTHORIZER={\\\"claims\\\":{\\\"permissions\\\":\\\"\"[view:accounts manage:accounts]\"\\\",\\\"sub\\\":\\\"auth0|5cfe0adce3c4c50ea072ea9f\\\"}} AWS_PROFILE=elit_nonprd serverless offline start -s dev --noAuth",
    "worksOnNix": "cross-env AUTHORIZER=\"{\\\"claims\\\":{\\\"permissions\\\":\\\"[view:accounts manage:accounts]\\\",\\\"sub\\\":\\\"auth0|5cfe0adce3c4c50ea072ea9f\\\"}}\" AWS_PROFILE=elit_nonprd serverless offline start -s dev --noAuth",
  }
}

Solution

To address your requirement for a single syntax cross-platform I would consider a different approach by utilizing a node.js script instead.

  1. Save this serve.js script (below) in the root of your project directory, i.e. save it at the same level where package.json resides.

    serve.js

    const spawn = require('child_process').spawn;
    
    const processEnv = process.env;
    
    processEnv.AUTHORIZER = '{"claims":{"permissions":"[view:accounts manage:accounts]","sub":"auth0|5cfe0adce3c4c50ea072ea9f"}}';
    processEnv.AWS_PROFILE = 'elit_nonprd';
    
    spawn('serverless', ['offline', 'start', '-s', 'dev', '--noAuth'], {
      env: processEnv,
      stdio: 'inherit',
      shell: true
    });
    
  2. In the scripts section of your package.json redefine your serve script as follows:

    package.json

    {
      ...
      "scripts": {
        "serve": "node serve"
      }
      ...
    }
    
  3. Run the following command:

    npm run serve
    

Explanation

The following explains what's happening in serve.js:

  • Firstly we require the child_process module's spawn() method.

  • The part that reads;

    const processEnv = process.env;
    
    processEnv.AUTHORIZER = '{"claims":{"permissions":"[view:accounts manage:accounts]","sub":"auth0|5cfe0adce3c4c50ea072ea9f"}}';
    processEnv.AWS_PROFILE = 'elit_nonprd';
    

    obtains the existing environment variables, using process.env, and assigns them to the processEnv variable.

    Subsequently, we augment the processEnv object with the AUTHORIZER and AWS_PROFILE properties and their necessary values. This essentially defines the two new Environment variables.

  • Finally we "shell out" the serverless offline start -s dev --noAuth command via child_process.spawn().

    • The { env: processEnv } part sets child_process.spawn's env option to the processEnv object, i.e. it sets the environment variables for the child process.

    • The stdio option configures the pipes for stdin, stdout, stderr in the child process. This ensures you get any logging in the console.

    • The shell option is set to true.

Note: The cross-env package becomes redundant using this solution.

Upvotes: 2

Related Questions