jest-dynamodb icon indicating copy to clipboard operation
jest-dynamodb copied to clipboard

Dynamic table resolving from `serverless.yml` after upgrade to Serverless 3

Open jenssimon opened this issue 2 years ago • 4 comments

I try to upgrade a project to Serverless 3 which uses this code from readme in jest-dynamodb-config.js.

module.exports = async () => {
  const serverless = new (require('serverless'))();
  // If using monorepo where DynamoDB serverless.yml is in another directory
  // const serverless = new (require('serverless'))({ servicePath: '../../../core/data' });

  await serverless.init();
  const service = await serverless.variables.populateService();
  const resources = service.resources.filter(r => Object.keys(r).includes('Resources'))[0];

  const tables = Object.keys(resources)
    .map(name => resources[name])
    .filter(r => r.Type === 'AWS::DynamoDB::Table')
    .map(r => r.Properties);

  return {
    tables,
    port: 8000
  };
};

Works perfect for Serverless 2. But with Serverless 3 I get this error:

TypeError: Jest: Got error running globalSetup - /path/to/project/node_modules/@shelf/jest-dynamodb/setup.js, reason: undefined is not an array
    at module.exports (/path/to/project/node_modules/type/lib/resolve-exception.js:12:14)
    at module.exports (/path/to/project/node_modules/type/array/ensure.js:15:25)
    at new Serverless (/path/to/project/node_modules/serverless/lib/serverless.js:84:22)
    at module.exports (/path/to/project/jest-dynamodb-config.js:4:24)
    at module.exports (/path/to/project/node_modules/@shelf/jest-dynamodb/setup.js:21:44)
    at /path/to/project/node_modules/@jest/core/build/runGlobalHook.js:125:19
    at ScriptTransformer.requireAndTranspileModule (/path/to/project/node_modules/@jest/transform/build/ScriptTransformer.js:903:24)
    at async _default (/path/to/project/node_modules/@jest/core/build/runGlobalHook.js:116:9)
    at async runJest (/path/to/project/node_modules/@jest/core/build/runJest.js:369:5)
    at async _run10000 (/path/to/project/node_modules/@jest/core/build/cli/index.js:320:7)
    at async runCLI (/path/to/project/node_modules/@jest/core/build/cli/index.js:173:3)
    at async Object.run (/path/to/project/node_modules/jest-cli/build/cli/index.js:155:37)

Is there an updated version of that code that works with Serverless 3?

Thanks!

jenssimon avatar Feb 04 '22 10:02 jenssimon

Same here! I couldn't find a solution or workaround.

robsonmafra avatar Feb 26 '22 01:02 robsonmafra

@jenssimon , did you find a solution? I found here the details about the Serverless initialisation after version 3:

Serverless constructor was refactored to depend on service configuration being resolved externally and passed to its constructor with following options: configuration - Service configuration (JSON serializable plain object) serviceDir - Directory in which service is placed (All path references in service configuration will be resolved against this path) configurationFilename - Name of configuration file (e.g. serverless.yml).

Starting from v3.0.0 configuration data will not be resolved internally, and if Serverless is invoked in service context, all three options will have to be provided Reference: https://www.serverless.com/framework/docs/deprecations#serverless-constructor-service-configuration-dependency

So, I just initialised manually the required variables, but it stills not load as the serverless.variables.populateService(); was also removed. I will share here in case that helps:

const Serverless = require('serverless');
const path = require('path');
const fs = require('fs');
const yaml = require('js-yaml');

module.exports = async () => {
  const serviceDir = process.cwd();
  const configurationFilename = 'serverless.yml';
  const configuration = yaml.load(fs.readFileSync(path.resolve(serviceDir, configurationFilename), 'utf8'));
  const serverless = new Serverless({ configuration, serviceDir, configurationFilename, commands: [], options: {} });
  await serverless.init();
  const resources = serverless.service.resources.Resources;

  const tables = Object.keys(resources)
    .map((name) => resources[name])
    .filter((r) => r.Type === 'AWS::DynamoDB::Table')
    .map((r) => r.Properties);

  return {
    tables,
    port: 8000,
  };
};

robsonmafra avatar Feb 26 '22 02:02 robsonmafra

Thanks @robsonmafra for your input.

Haven't had time to check on this for a while. I decided to process the configuration without Serverless and take care of the variables by myself. In my case I use some hard coded values for now.

But would be nice to have a smarter generic solution (as it was for serverless < 3).

jenssimon avatar Mar 15 '22 09:03 jenssimon

A more general method is using output of the print command, which helps if you've used subsitutions in your yaml, or have defined it in TypeScript and want to consume it in JavaScript.

For example:

const path = require("path")
const execSync = require('child_process').execSync;

module.exports = async () => {
  const service = JSON.parse(execSync('npx serverless print --format json', { encoding: 'utf-8' }))

  const tables = Object.values(service.resources.Resources)
    .filter((resource) => resource.Type === "AWS::DynamoDB::Table")
    .map((resource) => resource.Properties)

  return {
    tables,
    port: 8000,
  }
}

domdomegg avatar May 15 '22 17:05 domdomegg