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

serverless-heaven / serverless-webpack / 28932671810

08 Jul 2026 09:34AM UTC coverage: 90.515% (-2.4%) from 92.918%
28932671810

push

github

web-flow
Merge pull request #2431 from serverless-heaven/dependabot/npm_and_yarn/archiver-8.0.0

build(deps): bump archiver from 7.0.1 to 8.0.0

989 of 1157 branches covered (85.48%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

55 existing lines in 7 files now uncovered.

2580 of 2786 relevant lines covered (92.61%)

27.25 hits per line

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

91.39
/lib/packageModules.js
1
const _ = require('lodash');
2✔
2
const path = require('node:path');
2✔
3
const glob = require('glob');
2✔
4
const { ZipArchive } = 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) {
38✔
14
  const version = this.serverless.getVersion();
38✔
15

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

38✔
22
  // Serverless changed the artifact path location in version 1.18
38✔
23
  if (semver.lt(version, '1.18.0')) {
38✔
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 {
38✔
30
    func.package = {
26✔
31
      artifact: artifactPath
26✔
32
    };
26✔
33
  }
26✔
34
}
38✔
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) {
22✔
42
  const { artifactFilePath, directory, files } = args;
22✔
43

22✔
44
  const zip = new ZipArchive();
22✔
45
  const output = fs.createWriteStream(artifactFilePath);
22✔
46

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

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

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

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

22✔
79
        zip.finalize();
22✔
80
      } catch (error) {
22!
UNCOV
81
        reject(error);
×
UNCOV
82
      }
×
83
    });
22✔
84
  });
22✔
85
}
22✔
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) {
102✔
93
  const fullPath = `${directory}/${filePath}`;
102✔
94

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

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

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

26✔
125
  // if excludeRegex option is defined, we'll have to list all files to be zipped
26✔
126
  // and then force the node way to zip to avoid hitting the arguments limit (ie: E2BIG)
26✔
127
  // when using the native way (ie: the zip command)
26✔
128
  if (this.configuration.excludeRegex) {
26✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

22✔
208
      if (this.log) {
26!
UNCOV
209
        this.log.verbose(
×
UNCOV
210
          `Zip ${_.isEmpty(entryFunction) ? 'service' : 'function'}: ${modulePath} [${_.now() - startZip} ms]`
×
UNCOV
211
        );
×
212
      } else {
26✔
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
    }
26✔
219

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

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

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

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

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

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

20✔
258
    // If we are deploying to 'google' we need to set an artifact for the whole service,
20✔
259
    // rather than for each function, so there is special case here
20✔
260
    if (!individualPackagingEnabled && providerIsGoogle) {
20✔
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
  }
20✔
269
};
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc