Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid assignment on 0.28.x (superagent as dependency) #697

Open
talaikis opened this issue Apr 22, 2021 · 10 comments
Open

Invalid assignment on 0.28.x (superagent as dependency) #697

talaikis opened this issue Apr 22, 2021 · 10 comments

Comments

@talaikis
Copy link

Works fine with <= 0.27.0, when 0.28.x does not build one file, that has superagent as dependency:

...\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20
<code>
JS_Parse_Error [SyntaxError]: Invalid assignment
    at js_error (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:163522)
    at croak (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:174584)
    at maybe_assign (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:204547)
    at expression (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:204656)
    at simple_statement (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:179275)
    at statement (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:176763)
    at _embed_tokens_wrapper (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:175715)
    at if_ (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:187029)
    at statement (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:177758)
    at _embed_tokens_wrapper (\node_modules\@vercel\ncc\dist\ncc/index.js.cache.js:20:175715) {
  filename: '0',
  line: 2338,
  col: 45,
  pos: 54545
}
@guybedford
Copy link
Contributor

Thanks for posting the issue - can you share some more details on the replication here? I tried the following build:

test.js

console.log(require('superagent'));

with superagent@6.1.0, and ncc run test.js seems to work fine for me.

@talaikis
Copy link
Author

I see, ok, here;s full file then:

// used @mailchimp/mailchimp_marketing
const superagent = require('superagent')
const querystring = require('querystring')
const { createHash } = require('crypto')

const md5 = (s) => {
  const md5 = createHash('md5')
  md5.update(s)
  return md5.digest('hex')
}

const config = {
  apiKey: '<removed>,
  server: 'us2'
}

module.exports.addSubsriber = async (email, fName, lName) => {
  await addListMember('<rm>', {
    email_address: email,
    status: 'subscribed',
    update_existing: true,
    merge_fields: {
      FNAME: fName,
      LNAME: lName
    }
  }).catch((e) => {
    // console.log('e', e, typeof e)
    if (!(e && e.response && e.response.text && e.response.text.includes('Member Exists'))) {
      throw new Error(e)
    }
  })
}

module.exports.rmSubsriber = async (email) => {
  try {
    await deleteListMember('<rm>', md5(email))
  } catch (e) {
    console.log(e)
  }
}

const addListMember = async (listId, body, opts) => {
  opts = opts || {}
  const postBody = body

  const pathParams = { list_id: listId }
  const queryParams = { skip_merge_validation: opts.skipMergeValidation }
  const headerParams = {}
  const formParams = {}
  const authNames = ['bsicAuth']
  const contentTypes = ['application/json']
  const accepts = ['application/json', 'application/problem+json']
  const returnType = 'application/json'

  return await callApi(
    '/lists/{list_id}/members', 'POST',
    pathParams, queryParams, headerParams, formParams, postBody,
    authNames, contentTypes, accepts, returnType
  )
}

const deleteListMember = async (listId, subscriberHash) => {
  const postBody = null
  const pathParams = { list_id: listId, subscriber_hash: subscriberHash }
  const queryParams = {}
  const headerParams = {}
  const formParams = {}
  const authNames = ['basicAuth']
  const contentTypes = ['application/json']
  const accepts = ['application/json', 'application/problem+json']
  const returnType = 'application/json'

  return await callApi(
    '/lists/{list_id}/members/{subscriber_hash}', 'DELETE',
    pathParams, queryParams, headerParams, formParams, postBody,
    authNames, contentTypes, accepts, returnType
  )
}
const paramToString = function (param) {
  if (param === undefined || param == null) return ''
  if (param instanceof Date) return param.toJSON()
  return param.toString()
}

const buildUrl = function (path, pathParams) {
  const basePath = 'https://server.api.mailchimp.com/3.0'.replace(/\/+$/, '')

  if (!path.match(/^\//)) {
    path = '/' + path
  }
  let url = basePath + path
  url = url.replace(/\{([\w-]+)\}/g, function (fullMatch, key) {
    let value
    // eslint-disable-next-line
    if (pathParams.hasOwnProperty(key)) {
      value = paramToString(pathParams[key])
    } else {
      value = fullMatch
    }
    return encodeURIComponent(value)
  })

  // Define the server
  if (typeof config.server !== 'undefined') {
    url = url.replace('server', config.server)
  }

  return url
}

const normalizeParams = function (params) {
  const newParams = {}
  for (const key in params) {
    // eslint-disable-next-line
    if (params.hasOwnProperty(key) && params[key] !== undefined && params[key] !== null) {
      const value = params[key]
      if (isFileParam(value) || Array.isArray(value)) {
        newParams[key] = value
      } else {
        newParams[key] = paramToString(value)
      }
    }
  }
  return newParams
}
const isJsonMime = function (contentType) {
  return Boolean(contentType != null && contentType.match(/^application\/json(;.*)?$/i))
}

const jsonPreferredMime = function (contentTypes) {
  for (let i = 0; i < contentTypes.length; i++) {
    if (isJsonMime(contentTypes[i])) {
      return contentTypes[i]
    }
  }
  return contentTypes[0]
}

const callApi = function callApi (path, httpMethod, pathParams, queryParams, headerParams, formParams, bodyParam, authNames, contentTypes, accepts, returnType) {
  const url = buildUrl(path, pathParams)
  const request = superagent(httpMethod, url)
  const cache = true
  const timeout = 120000

  // Basic Authentication
  if (config.apiKey !== undefined && config.apiKey !== '') {
    request.auth('user', config.apiKey)
  } else if (config.accessToken !== undefined && config.accessToken !== '') {
    request.set({ Authorization: 'Bearer ' + config.accessToken })
  }

  // set query parameters
  if (httpMethod.toUpperCase() === 'GET' && cache === false) {
    // eslint-disable-next-line
    queryParams['_'] = new Date().getTime()
  }
  request.query(normalizeParams(queryParams))
  request.set({}).set(normalizeParams(headerParams))
  request.timeout(timeout)

  const contentType = jsonPreferredMime(contentTypes)
  if (contentType) {
    // Issue with superagent and multipart/form-data (https://github.com/visionmedia/superagent/issues/746)
    if (contentType !== 'multipart/form-data') {
      request.type(contentType)
    }
  } else {
    request.type('application/json')
  }

  if (contentType === 'application/x-www-form-urlencoded') {
    request.send(querystring.stringify(normalizeParams(formParams)))
  } else if (contentType === 'multipart/form-data') {
    const _formParams = normalizeParams(formParams)
    for (const key in _formParams) {
      // eslint-disable-next-line
      if (_formParams.hasOwnProperty(key)) {
        if (isFileParam(_formParams[key])) {
          // file field
          request.attach(key, _formParams[key])
        } else {
          request.field(key, _formParams[key])
        }
      }
    }
  } else if (bodyParam) {
    request.send(bodyParam)
  }

  const accept = jsonPreferredMime(accepts)
  if (accept) {
    request.accept(accept)
  }

  if (returnType === 'Blob') {
    request.responseType('blob')
  } else if (returnType === 'String') {
    request.responseType('string')
  }

  return new Promise(function (resolve, reject) {
    request.end(function (error, response) {
      if (error) {
        reject(error)
      } else {
        try {
          const data = deserialize(response, returnType)
          resolve({ data: data, response: response })
        } catch (err) {
          reject(err)
        }
      }
    })
  })
}

const isFileParam = function (param) {
  // fs.ReadStream in Node.js and Electron (but not in runtime like browserify)
  if (typeof require === 'function') {
    let fs
    try {
      fs = require('fs')
    } catch (err) {}
    if (fs && fs.ReadStream && param instanceof fs.ReadStream) {
      return true
    }
  }
  // Buffer in Node.js
  if (typeof Buffer === 'function' && param instanceof Buffer) {
    return true
  }
  // Blob in browser
  // eslint-disable-next-line
  if (typeof Blob === 'function' && param instanceof Blob) {
    return true
  }
  // File in browser (it seems File object is also instance of Blob, but keep this for safe)
  // eslint-disable-next-line
  if (typeof File === 'function' && param instanceof File) {
    return true
  }
  return false
}

const deserialize = function deserialize (response, returnType) {
  if (response === null || returnType === null || response.status === 204) {
    return null
  }
  // Rely on SuperAgent for parsing response body.
  // See http://visionmedia.github.io/superagent/#parsing-response-bodies
  let data = response.body
  if (data == null || (typeof data === 'object' && typeof data.length === 'undefined' && !Object.keys(data).length)) {
    // SuperAgent does not always produce a body; use the unparsed response as a fallback
    data = response.text
  }
  return convertToType(data, returnType)
}

const convertToType = function (data, type) {
  if (data === null || data === undefined) return data

  switch (type) {
    case 'Boolean':
      return Boolean(data)
    case 'Integer':
      return parseInt(data, 10)
    case 'Number':
      return parseFloat(data)
    case 'String':
      return String(data)
    case 'Date':
      return parseDate(String(data))
    case 'Blob':
      return data
    default:
      if (type === Object) {
        // generic object, return directly
        return data
      } else if (typeof type === 'function') {
        // for model type like: User
        return type.constructFromObject(data)
      } else if (Array.isArray(type)) {
        // for array type like: ['String']
        const itemType = type[0]
        return data.map(function (item) {
          return exports.convertToType(item, itemType)
        })
      } else if (typeof type === 'object') {
        // for plain object type like: {'String': 'Integer'}
        let keyType
        let valueType
        for (const ks in type) {
          // eslint-disable-next-line
          if (type.hasOwnProperty(ks)) {
            keyType = ks
            valueType = type[ks]
            break
          }
        }
        const result = {}
        for (const k in data) {
          // eslint-disable-next-line
          if (data.hasOwnProperty(k)) {
            const key = convertToType(k, keyType)
            const value = convertToType(data[k], valueType)
            result[key] = value
          }
        }
        return result
      } else {
        // for unknown type, return the data directly
        return data
      }
  }
}

const parseDate = function (str) {
  return new Date(str.replace(/T/i, ' '))
}

@guybedford
Copy link
Contributor

@talaikis if I build that file with ncc I get a successfull build. Can you please share the exact version of ncc you are using, what build script you are running and what operation is causing the error.

@talaikis
Copy link
Author

talaikis commented Apr 28, 2021

@talaikis if I build that file with ncc I get a successfull build. Can you please share the exact version of ncc you are using, what build script you are running and what operation is causing the error.

Windows 10
Node: v15.14.0
"@vercel/ncc": "0.28.3", // works fine with 0.27

build scr:

const { join } = require('path')
const { writeFile } = require('fs')

const functions = [
  { p: join(process.cwd(), 'handlers', 'utils', 'mailChimp.js'), o: join(process.cwd(), 'build', 'mailchimp.js') },
]

(async () => {
  for (const func of functions) {
    require('@vercel/ncc')(func.p, {
      cache: false,
      externals: [],
      filterAssetBase: process.cwd(),
      minify: true,
      sourceMap: false,
      sourceMapBasePrefix: '../',
      sourceMapRegister: false,
      watch: false,
      v8cache: false,
      quiet: false,
      debugLog: false
    }).then(({ code }) => {
      writeFile(func.o, code, (e) => {
        if (e) {
          process.exit(1)
        }
      })
    })
      .catch((e) => console.log(e))
  }

})()

@guybedford
Copy link
Contributor

Thanks, I can replicate the issue but only on the release version not the master branch.

As a result, I believe the next release should resolve this being posted shortly.

@styfle
Copy link
Member

styfle commented Apr 28, 2021

Can you confirm 0.28.4 fixes the issue?

@talaikis
Copy link
Author

Can you confirm 0.28.4 fixes the issue?

Same error.

@guybedford
Copy link
Contributor

@talaikis I've posted my replication attempt at https://github.com/guybedford/ncc-697, and am unable to see the issue there on 0.28.4. Can you verify if the replication is working correctly for you? And if so what changes need to be made to the replication to see the bug.

@guybedford
Copy link
Contributor

Ok I managed to get the replication again, looking into it...

@guybedford
Copy link
Contributor

Posted a fix in #703. The message here is a Terser build error, which was also happening in 0.27 but we don't cause Terser errors to fail the build. As to why the Terser error is happening in the first place I still need to track that down as well - we can possibly leave this issue open for that.

styfle pushed a commit that referenced this issue Apr 29, 2021
With the Terser upgrade in #669 the error handling needed to be updated to handle the new async form of the function.

As a result Terser errors were becoming build errors, which is fixed here.

This resolves the Terser error regression from #697.
musaevonline added a commit to musaevonline/react-animation-ncc that referenced this issue Dec 4, 2021
With the Terser upgrade in vercel/ncc#669 the error handling needed to be updated to handle the new async form of the function.

As a result Terser errors were becoming build errors, which is fixed here.

This resolves the Terser error regression from vercel/ncc#697.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants