• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

serverless-heaven / serverless-webpack / 24743128938

21 Apr 2026 07:51PM UTC coverage: 91.605% (-1.1%) from 92.749%
24743128938

Pull #2409

github

web-flow
Merge 7c5ae6af1 into 1b2f2ee5a
Pull Request #2409: Remove bluebird and use native Promise

1005 of 1145 branches covered (87.77%)

Branch coverage included in aggregate %.

569 of 631 new or added lines in 13 files covered. (90.17%)

2 existing lines in 1 file now uncovered.

2563 of 2750 relevant lines covered (93.2%)

72.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.48
/lib/packageModules.js
1
const _ = require('lodash');
2✔
2
const path = require('node:path');
2✔
3
const glob = require('glob');
2✔
4
const archiver = require('archiver');
2✔
5
const semver = require('semver');
2✔
6
const fs = require('node:fs');
2✔
7
const { promisify } = require('node:util');
2✔
8
const { getAllNodeFunctions, isProviderGoogle } = require('./utils');
2✔
9

2✔
10
const readFileAsync = promisify(fs.readFile);
2✔
11
const statAsync = promisify(fs.stat);
2✔
12

2✔
13
function setArtifactPath(funcName, func, artifactPath) {
44✔
14
  const version = this.serverless.getVersion();
44✔
15

44✔
16
  if (this.log) {
44✔
17
    this.log.verbose(`Setting artifact for function '${funcName}' to '${artifactPath}'`);
6✔
18
  } else {
44✔
19
    this.options.verbose && this.serverless.cli.log(`Setting artifact for function '${funcName}' to '${artifactPath}'`);
38✔
20
  }
38✔
21

44✔
22
  // Serverless changed the artifact path location in version 1.18
44✔
23
  if (semver.lt(version, '1.18.0')) {
44✔
24
    func.artifact = artifactPath;
12✔
25
    func.package = _.assign({}, func.package, { disable: true });
12✔
26
    if (!this.log) {
12✔
27
      this.serverless.cli.log(`${funcName} is packaged by the webpack plugin. Ignore messages from SLS.`);
12✔
28
    }
12✔
29
  } else {
44✔
30
    func.package = {
32✔
31
      artifact: artifactPath
32✔
32
    };
32✔
33
  }
32✔
34
}
44✔
35

2✔
36
/**
2✔
37
 * Copy pasted from Serverless
2✔
38
 *
2✔
39
 * @see https://github.com/serverless/serverless/blob/63d54e1537e10ae63c171892edd886f6b81e83f6/lib/plugins/package/lib/zipService.js#L65
2✔
40
 */
2✔
41
function serverlessZip(args) {
28✔
42
  const { artifactFilePath, directory, files } = args;
28✔
43

28✔
44
  const zip = archiver.create('zip');
28✔
45
  const output = fs.createWriteStream(artifactFilePath);
28✔
46

28✔
47
  return new Promise((resolve, reject) => {
28✔
48
    output.on('close', () => resolve(artifactFilePath));
28✔
49
    output.on('error', reject);
28✔
50
    zip.on('error', reject);
28✔
51

28✔
52
    output.on('open', async () => {
28✔
53
      try {
28✔
54
        zip.pipe(output);
28✔
55

28✔
56
        // normalize both maps to avoid problems with e.g. Path Separators in different shells
28✔
57
        const normalizedFiles = _.uniq(_.map(files, file => path.normalize(file)));
28✔
58
        const contents = await Promise.all(
28✔
59
          _.map(normalizedFiles, file => getFileContentAndStat.call(this, directory, file))
28✔
60
        );
28✔
61

6✔
62
        _.forEach(
6✔
63
          contents.sort((content1, content2) => content1.filePath.localeCompare(content2.filePath)),
6✔
64
          file => {
6✔
65
            const name = file.filePath;
4,278✔
66
            // Ensure file is executable if it is locally executable or
4,278✔
67
            // we force it to be executable if platform is windows
4,278✔
68
            const mode = file.stat.mode & 0o100 || process.platform === 'win32' ? 0o755 : 0o644;
4,278!
69
            zip.append(file.data, {
4,278✔
70
              name,
4,278✔
71
              mode,
4,278✔
72
              // necessary to get the same hash when zipping the same content
4,278✔
73
              // as well as `contents.sort` few lines above
4,278✔
74
              date: new Date(0)
4,278✔
75
            });
4,278✔
76
          }
4,278✔
77
        );
6✔
78

6✔
79
        zip.finalize();
6✔
80
      } catch (error) {
28✔
81
        reject(error);
22✔
82
      }
22✔
83
    });
28✔
84
  });
28✔
85
}
28✔
86

2✔
87
/**
2✔
88
 * Copy pasted from Serverless
2✔
89
 *
2✔
90
 * @see https://github.com/serverless/serverless/blob/63d54e1537e10ae63c171892edd886f6b81e83f6/lib/plugins/package/lib/zipService.js#L112
2✔
91
 */
2✔
92
async function getFileContentAndStat(directory, filePath) {
4,278✔
93
  const fullPath = `${directory}/${filePath}`;
4,278✔
94

4,278✔
95
  try {
4,278✔
96
    const [data, stat] = await Promise.all([
4,278✔
97
      // Get file contents and stat in parallel
4,278✔
98
      readFileAsync(fullPath),
4,278✔
99
      statAsync(fullPath)
4,278✔
100
    ]);
4,278✔
101

4,278✔
102
    return {
4,278✔
103
      data,
4,278✔
104
      stat,
4,278✔
105
      filePath
4,278✔
106
    };
4,278✔
107
  } catch (error) {
4,278!
NEW
108
    throw new this.serverless.classes.Error(
×
NEW
109
      `Cannot read file ${filePath} due to: ${error.message}`,
×
NEW
110
      'CANNOT_READ_FILE'
×
NEW
111
    );
×
NEW
112
  }
×
113
}
4,278✔
114

2✔
115
async function zip(directory, zipFileName) {
32✔
116
  // Check that files exist to be zipped
32✔
117
  let files = glob.sync('**', {
32✔
118
    cwd: directory,
32✔
119
    dot: true,
32✔
120
    silent: true,
32✔
121
    follow: true,
32✔
122
    nodir: true
32✔
123
  });
32✔
124

32✔
125
  // if excludeRegex option is defined, we'll have to list all files to be zipped
32✔
126
  // and then force the node way to zip to avoid hitting the arguments limit (ie: E2BIG)
32✔
127
  // when using the native way (ie: the zip command)
32✔
128
  if (this.configuration.excludeRegex) {
32✔
129
    const existingFilesLength = files.length;
4✔
130
    files = _.filter(files, f => f.match(this.configuration.excludeRegex) === null);
4✔
131

4✔
132
    if (this.log) {
4!
133
      this.log.verbose(`Excluded ${existingFilesLength - files.length} file(s) based on excludeRegex`);
×
134
    } else {
4✔
135
      this.options.verbose &&
4✔
136
        this.serverless.cli.log(`Excluded ${existingFilesLength - files.length} file(s) based on excludeRegex`);
4✔
137
    }
4✔
138
  }
4✔
139

32✔
140
  if (_.isEmpty(files)) {
32✔
141
    throw new this.serverless.classes.Error('Packaging: No files found');
4✔
142
  }
4✔
143

28✔
144
  // Create artifact in temp path and move it to the package path (if any) later
28✔
145
  // This allows us to persist the webpackOutputPath and re-use the compiled output
28✔
146
  const artifactFilePath = path.join(this.webpackOutputPath, zipFileName);
28✔
147
  this.serverless.utils.writeFileDir(artifactFilePath);
28✔
148

28✔
149
  return serverlessZip.call(this, {
28✔
150
    directory,
28✔
151
    artifactFilePath,
28✔
152
    files
28✔
153
  });
28✔
154
}
28✔
155

2✔
156
function getArtifactLocations(name) {
74✔
157
  const archiveName = `${name}.zip`;
74✔
158

74✔
159
  const webpackArtifact = path.join(this.webpackOutputPath, archiveName);
74✔
160
  const serverlessArtifact = path.join('.serverless', archiveName);
74✔
161

74✔
162
  return { webpackArtifact, serverlessArtifact };
74✔
163
}
74✔
164

2✔
165
function copyArtifactByName(artifactName) {
28✔
166
  const { webpackArtifact, serverlessArtifact } = getArtifactLocations.call(this, artifactName);
28✔
167

28✔
168
  // Make sure the destination dir exists
28✔
169
  this.serverless.utils.writeFileDir(serverlessArtifact);
28✔
170

28✔
171
  fs.copyFileSync(webpackArtifact, serverlessArtifact);
28✔
172
}
28✔
173

2✔
174
function setServiceArtifactPath(artifactPath) {
2✔
175
  _.set(this.serverless, 'service.package.artifact', artifactPath);
2✔
176
}
2✔
177

2✔
178
function isIndividualPackaging() {
26✔
179
  return _.get(this.serverless, 'service.package.individually');
26✔
180
}
26✔
181

2✔
182
function getArtifactName(entryFunction) {
32✔
183
  return `${entryFunction.funcName || this.serverless.service.getServiceObject().name}.zip`;
32✔
184
}
32✔
185

2✔
186
module.exports = {
2✔
187
  async packageModules() {
2✔
188
    if (this.skipCompile) {
34✔
189
      return;
2✔
190
    }
2✔
191
    if (this.log) {
34✔
192
      this.log.verbose('[Webpack] Packaging modules');
6✔
193
      this.progress.get('webpack').notice('[Webpack] Packaging modules');
6✔
194
    }
6✔
195

32✔
196
    const stats = this.compileStats;
32✔
197
    const zippedArtifacts = [];
32✔
198

32✔
199
    for (const [index, compileStats] of stats.stats.entries()) {
32✔
200
      const entryFunction = _.get(this.entryFunctions, index, {});
32✔
201
      const filename = getArtifactName.call(this, entryFunction);
32✔
202
      const modulePath = compileStats.outputPath;
32✔
203

32✔
204
      const startZip = _.now();
32✔
205
      const artifactPath = await zip.call(this, modulePath, filename);
32✔
206
      zippedArtifacts.push(artifactPath);
28✔
207

28✔
208
      if (this.log) {
32✔
209
        this.log.verbose(
6✔
210
          `Zip ${_.isEmpty(entryFunction) ? 'service' : 'function'}: ${modulePath} [${_.now() - startZip} ms]`
6!
211
        );
6✔
212
      } else {
32✔
213
        this.options.verbose &&
22✔
214
          this.serverless.cli.log(
20✔
215
            `Zip ${_.isEmpty(entryFunction) ? 'service' : 'function'}: ${modulePath} [${_.now() - startZip} ms]`
20✔
216
          );
22✔
217
      }
22✔
218
    }
32✔
219

28✔
220
    return zippedArtifacts;
28✔
221
  },
2✔
222

2✔
223
  async copyExistingArtifacts() {
2✔
224
    if (this.log) {
26✔
225
      this.log.verbose('[Webpack] Copying existing artifacts');
6✔
226
      this.progress.get('webpack').notice('[Webpack] Copying existing artifacts');
6✔
227
    } else {
26✔
228
      this.serverless.cli.log('Copying existing artifacts...');
20✔
229
    }
20✔
230
    // When invoked as a part of `deploy function`,
26✔
231
    // only function passed with `-f` flag should be processed.
26✔
232
    const functionNames = this.options.function ? [this.options.function] : getAllNodeFunctions.call(this);
26✔
233
    const serviceName = this.serverless.service.getServiceObject().name;
26✔
234
    const individualPackagingEnabled = isIndividualPackaging.call(this);
26✔
235
    const providerIsGoogle = isProviderGoogle(this.serverless);
26✔
236

26✔
237
    // Copy artifacts to package location
26✔
238
    if (individualPackagingEnabled) {
26✔
239
      _.forEach(functionNames, funcName => copyArtifactByName.call(this, funcName));
4✔
240
    } else {
26✔
241
      // Copy service packaged artifact
22✔
242
      copyArtifactByName.call(this, serviceName);
22✔
243
    }
22✔
244

26✔
245
    // Loop through every function and make sure that the correct artifact is assigned
26✔
246
    // (the one built by webpack)
26✔
247
    _.forEach(functionNames, funcName => {
26✔
248
      const func = this.serverless.service.getFunction(funcName);
44✔
249

44✔
250
      // When individual packaging is enabled, each functions gets it's own
44✔
251
      // artifact, otherwise every function gets set to the same artifact
44✔
252
      const archiveName = individualPackagingEnabled ? funcName : serviceName;
44✔
253

44✔
254
      const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
44✔
255
      setArtifactPath.call(this, funcName, func, serverlessArtifact);
44✔
256
    });
26✔
257

26✔
258
    // If we are deploying to 'google' we need to set an artifact for the whole service,
26✔
259
    // rather than for each function, so there is special case here
26✔
260
    if (!individualPackagingEnabled && providerIsGoogle) {
26✔
261
      const archiveName = serviceName;
2✔
262

2✔
263
      // This may look similar to the loop above, but note that this calls
2✔
264
      // setServiceArtifactPath rather than setArtifactPath
2✔
265
      const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
2✔
266
      setServiceArtifactPath.call(this, serverlessArtifact);
2✔
267
    }
2✔
268
  }
26✔
269
};
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc