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

serverless-heaven / serverless-webpack / 16157125301

09 Jul 2025 12:13AM UTC coverage: 91.874% (-0.5%) from 92.411%
16157125301

Pull #2175

github

web-flow
Merge 7c5342d2c into 91d31ed0e
Pull Request #2175: build(deps-dev): bump @types/node from 22.15.30 to 24.0.12

968 of 1098 branches covered (88.16%)

Branch coverage included in aggregate %.

2571 of 2754 relevant lines covered (93.36%)

68.65 hits per line

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

92.82
/lib/packageModules.js
1
'use strict';
2✔
2

2✔
3
const BbPromise = require('bluebird');
2✔
4
const _ = require('lodash');
2✔
5
const path = require('path');
2✔
6
const glob = require('glob');
2✔
7
const archiver = require('archiver');
2✔
8
const semver = require('semver');
2✔
9
const fs = require('fs');
2✔
10
const { getAllNodeFunctions, isProviderGoogle } = require('./utils');
2✔
11

2✔
12
const readFileAsync = BbPromise.promisify(fs.readFile);
2✔
13
const statAsync = BbPromise.promisify(fs.stat);
2✔
14

2✔
15
function setArtifactPath(funcName, func, artifactPath) {
38✔
16
  const version = this.serverless.getVersion();
38✔
17

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

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

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

22✔
46
  const zip = archiver.create('zip');
22✔
47
  const output = fs.createWriteStream(artifactFilePath);
22✔
48

22✔
49
  return new BbPromise((resolve, reject) => {
22✔
50
    output.on('close', () => resolve(artifactFilePath));
22✔
51
    output.on('error', err => reject(err));
22✔
52
    zip.on('error', err => reject(err));
22✔
53

22✔
54
    output.on('open', () => {
22✔
55
      zip.pipe(output);
22✔
56

22✔
57
      // normalize both maps to avoid problems with e.g. Path Separators in different shells
22✔
58
      const normalizedFiles = _.uniq(_.map(files, file => path.normalize(file)));
22✔
59

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

6✔
79
          return zip.finalize();
6✔
80
        })
22✔
81
        .catch(reject);
22✔
82
    });
22✔
83
  });
22✔
84
}
22✔
85

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

4,292✔
94
  return BbPromise.all([
4,292✔
95
    // Get file contents and stat in parallel
4,292✔
96
    readFileAsync(fullPath),
4,292✔
97
    statAsync(fullPath)
4,292✔
98
  ]).then(
4,292✔
99
    result => ({
4,292✔
100
      data: result[0],
4,292✔
101
      stat: result[1],
4,292✔
102
      filePath
4,292✔
103
    }),
4,292✔
104
    error => {
4,292✔
105
      throw new this.serverless.classes.Error(
×
106
        `Cannot read file ${filePath} due to: ${error.message}`,
×
107
        'CANNOT_READ_FILE'
×
108
      );
×
109
    }
×
110
  );
4,292✔
111
}
4,292✔
112

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

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

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

26✔
138
  if (_.isEmpty(files)) {
26✔
139
    const error = new this.serverless.classes.Error('Packaging: No files found');
4✔
140

4✔
141
    return BbPromise.reject(error);
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
  packageModules() {
2✔
188
    if (this.skipCompile) {
28✔
189
      return BbPromise.resolve();
2✔
190
    }
2✔
191
    if (this.log) {
28!
192
      this.log.verbose('[Webpack] Packaging modules');
×
193
      this.progress.get('webpack').notice('[Webpack] Packaging modules');
×
194
    }
✔
195

26✔
196
    const stats = this.compileStats;
26✔
197

26✔
198
    return BbPromise.mapSeries(stats.stats, (compileStats, index) => {
26✔
199
      const entryFunction = _.get(this.entryFunctions, index, {});
26✔
200
      const filename = getArtifactName.call(this, entryFunction);
26✔
201
      const modulePath = compileStats.outputPath;
26✔
202

26✔
203
      const startZip = _.now();
26✔
204
      return zip.call(this, modulePath, filename).tap(() => {
26✔
205
        if (this.log) {
22!
206
          this.log.verbose(
×
207
            `Zip ${_.isEmpty(entryFunction) ? 'service' : 'function'}: ${modulePath} [${_.now() - startZip} ms]`
×
208
          );
×
209
        } else {
22✔
210
          this.options.verbose &&
22✔
211
            this.serverless.cli.log(
20✔
212
              `Zip ${_.isEmpty(entryFunction) ? 'service' : 'function'}: ${modulePath} [${_.now() - startZip} ms]`
20✔
213
            );
22✔
214
        }
22✔
215
      });
26✔
216
    });
26✔
217
  },
2✔
218

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

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

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

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

38✔
250
      const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
38✔
251
      setArtifactPath.call(this, funcName, func, serverlessArtifact);
38✔
252
    });
20✔
253

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

2✔
259
      // This may look similar to the loop above, but note that this calls
2✔
260
      // setServiceArtifactPath rather than setArtifactPath
2✔
261
      const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
2✔
262
      setServiceArtifactPath.call(this, serverlessArtifact);
2✔
263
    }
2✔
264

20✔
265
    return BbPromise.resolve();
20✔
266
  }
20✔
267
};
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