aws / aws-sam-cli

CLI tool to build, test, debug, and deploy Serverless applications using AWS SAM
https://aws.amazon.com/serverless/sam/
Apache License 2.0
6.52k stars 1.17k forks source link

NODE_OPTIONS is overwritten when invoking Lambdas with debugger #3307

Open gnestor opened 3 years ago

gnestor commented 3 years ago

Description:

If I run a Lambda locally using aws-sam (sam local start-lambda --env-vars .env.json --debug-port 5858), the NODE_OPTIONS that I define in the environment variables are not available. I'm trying to pass --experimental-loader /var/task/https-loader.mjs specifically, and it works when invoking the Lambda without debugging enabled, but when I invoke with a debugger, the value of process.env.NODE_OPTIONS is --require "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/ms-vscode.js-debug/src/bootloader.bundle.js" (I'm using the debugger in VS Code). I tried adding "env": { "NODE_OPTIONS": "--experimental-loader /var/task/https-loader.mjs" } to the VS Code debugger configuration, and the value of process.env.NODE_OPTIONS is --require "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/ms-vscode.js-debug/src/bootloader.bundle.js" --experimental-loader /var/task/https-loader.mjs, but the experimental loader feature is not enabled.

I suspect that it's because the user's NODE_OPTIONS are being passed through to the node executable: https://github.com/aws/aws-sam-cli/blob/59f2d194b65360d5bdeb0b930ff4f8e6dbc7c809/samcli/local/docker/lambda_debug_settings.py#L141

Steps to reproduce:

template.yml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  hammies-api

Resources:
  MyFunction
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions
      Handler: index.handler
      Runtime: nodejs14.x
      Environment:
        Variables:
          NODE_OPTIONS: --experimental-loader /var/task/https-loader.mjs

functions.https-loader.mjs

import { get } from 'https'

export function resolve(specifier, context, defaultResolve) {
  const { parentURL = null } = context

  // Normally Node.js would error on specifiers starting with 'https://', so
  // this hook intercepts them and converts them into absolute URLs to be
  // passed along to the later hooks below.
  if (specifier.startsWith('https://')) {
    return {
      url: specifier,
    }
  } else if (parentURL && parentURL.startsWith('https://')) {
    return {
      url: new URL(specifier, parentURL).href,
    }
  }

  // Let Node.js handle all other specifiers.
  return defaultResolve(specifier, context, defaultResolve)
}

export function getFormat(url, context, defaultGetFormat) {
  // This loader assumes all network-provided JavaScript is ES module code.
  if (url.startsWith('https://')) {
    return {
      format: 'module',
    }
  }

  // Let Node.js handle all other URLs.
  return defaultGetFormat(url, context, defaultGetFormat)
}

export function getSource(url, context, defaultGetSource) {
  // For JavaScript to be loaded over the network, we need to fetch and
  // return it.
  if (url.startsWith('https://')) {
    return new Promise((resolve, reject) => {
      get(url, (res) => {
        let data = ''
        res.on('data', (chunk) => (data += chunk))
        res.on('end', () => {
          // Transform source code
          const source = data
            .replace(
              /main.variable\(observer\("(\w+)"\)\)\.define\("\w+", \[(.+)\], function\((.+)\)/g,
              `main.variable(observer("$1")).define("$1", [$2,"fetch"], function($3,fetch)`
            )
            .replace(/main\.import\("spreadsheet", child\d\);\n/g, '')
            .replace(/btoa\((.+)\)/g, `Buffer.from($1).toString('base64')`)
          resolve({ source })
        })
      }).on('error', (err) => reject(err))
    })
  }

  // Let Node.js handle all other URLs.
  return defaultGetSource(url, context, defaultGetSource)
}

functions/index.js

exports.handler = async (event) => {
  try {
    return 'success'
  } catch (error) {
    throw error
  }
}

Observed result:

ERROR   Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only file and data URLs are s} code: 'ERR_UNSUPPORTED_ESM_URL_SCHEME'Runtime.js:66:25) {73:17)15:27)e.js:791:11)

Expected result:

INFO  success

Additional environment details (Ex: Windows, Mac, Amazon Linux etc)

  1. OS: macOS 11.6
  2. sam --version: 1.30.0
  3. AWS region: us-east-1

Add --debug flag to command you are running

gnestor commented 3 years ago

If I prepend by the command with DEBUGGER_ARGS, I'm able to work around this:

DEBUGGER_ARGS='--experimental-loader /var/task/https-loader.mjs' sam local invoke --env-vars .env.json --debug-port 5858
mndeveci commented 3 years ago

Thanks for feedback @gnestor, just to confirm this is working with sam local invoke but it is not working when debugging is enabled, right?

If that is the case then we need to append to the NODE_OPTIONS environment variable when we are setting up the debugger options.

gnestor commented 3 years ago

@mndeveci Correct

stormsa commented 2 years ago

Hello same issue, node options are override and does not work when use with debugger for local invoke or start-api. I was not able to override node options with tsconfig-paths/register until @gnestor workaround (thank you btw) Please fix this issue when you can or modify the doc to add parameters in DEBUGGER_ARGS