serverless-plugin-go 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.cz.toml CHANGED
@@ -1,7 +1,7 @@
1
1
  [tool]
2
2
  [tool.commitizen]
3
3
  name = "cz_customize"
4
- version = "0.1.2"
4
+ version = "0.1.3"
5
5
  tag_format = "v$version"
6
6
  bump_message = "bump: release $current_version → $new_version [skip-ci]"
7
7
  update_changelog_on_bump = true
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## v0.1.3 (2026-11-10)
2
+
3
+
4
+ - fix: per-function GOARCH and deterministic zip artifacts (#12)
5
+ - - Build env is now computed per function with an explicit GOARCH
6
+ (arm64/amd64). Previously config.env was mutated, so once an arm64
7
+ function compiled, every following x86_64 function was built as ARM,
8
+ and x86_64 builds on Apple Silicon used the host arch.
9
+ - Zip entries use a fixed mtime so identical binaries yield
10
+ byte-identical artifacts (stable hashes across builds).
11
+ - Bump mocha 10 -> 12 (mocha 10 fails to start on Node 26).
12
+
1
13
  ## v0.1.2 (2026-03-31)
2
14
 
3
15
 
package/index.js CHANGED
@@ -12,6 +12,9 @@ const supportedRuntimes = ["provided.al2", "provided.al2023"];
12
12
 
13
13
  const logPrefix = "GoPlugin";
14
14
 
15
+ // DOS zip timestamps can't go before 1980.
16
+ const zipEpoch = new Date(1980, 0, 1);
17
+
15
18
  export default class Go {
16
19
  constructor(serverless, options) {
17
20
  this.readFileSync = readFileSync;
@@ -149,9 +152,12 @@ export default class Go {
149
152
  return;
150
153
  }
151
154
 
152
- if (arch === "arm64") {
153
- config.env["GOARCH"] = "arm64";
154
- }
155
+ // Per-function env: never mutate shared config, and always set GOARCH so the
156
+ // host arch (e.g. Apple Silicon) never leaks into an x86_64 Lambda.
157
+ const env = {
158
+ ...config.env,
159
+ GOARCH: arch === "arm64" ? "arm64" : "amd64",
160
+ };
155
161
 
156
162
  const absHandler = path.resolve(config.baseDir);
157
163
  const absBin = path.resolve(config.binDir);
@@ -172,7 +178,7 @@ export default class Go {
172
178
  compileBinPath = path.relative(cwd, compileBinPath);
173
179
  }
174
180
 
175
- this.logDebug(`[${name}] env: ${JSON.stringify(config.env)}`);
181
+ this.logDebug(`[${name}] env: ${JSON.stringify(env)}`);
176
182
  this.logDebug(`[${name}] cwd: ${cwd}`);
177
183
  this.logDebug(`[${name}] arch: ${arch}`);
178
184
 
@@ -181,7 +187,7 @@ export default class Go {
181
187
  compileBinPath,
182
188
  handler,
183
189
  cwd,
184
- config.env,
190
+ env,
185
191
  arch,
186
192
  );
187
193
 
@@ -261,6 +267,8 @@ export default class Go {
261
267
  */
262
268
  packageBootstrap(name, binPath) {
263
269
  this.zip.addFile("bootstrap", this.readFileSync(binPath), "", 0o755);
270
+ // Fixed mtime so identical binaries produce byte-identical zips (stable hashes).
271
+ this.zip.getEntry("bootstrap").header.time = zipEpoch;
264
272
 
265
273
  const zipPath = binPath + ".zip";
266
274
  this.zip.writeZip(zipPath);
package/index.test.js CHANGED
@@ -2,6 +2,11 @@ import sinon from "sinon";
2
2
  import chai from "chai";
3
3
  import sinonChai from "sinon-chai";
4
4
 
5
+ import AdmZip from "adm-zip";
6
+ import fs from "fs";
7
+ import os from "os";
8
+ import path from "path";
9
+
5
10
  import Go from "./index.js";
6
11
  import ServerlessMock from "./mocks/serverless.mock.js";
7
12
 
@@ -27,6 +32,7 @@ describe("Go Plugin", () => {
27
32
  admZipStub = {
28
33
  addFile: sinon.stub(),
29
34
  writeZip: sinon.stub(),
35
+ getEntry: sinon.stub().returns({ header: {} }),
30
36
  };
31
37
  });
32
38
 
@@ -86,7 +92,10 @@ describe("Go Plugin", () => {
86
92
  `go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
87
93
  {
88
94
  cwd: ".",
89
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
95
+ env: {
96
+ ...process.env,
97
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
98
+ },
90
99
  },
91
100
  );
92
101
 
@@ -98,7 +107,10 @@ describe("Go Plugin", () => {
98
107
  `go build -ldflags="-s -w" -o .bin/testFunc3 functions/func3`,
99
108
  {
100
109
  cwd: ".",
101
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
110
+ env: {
111
+ ...process.env,
112
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
113
+ },
102
114
  },
103
115
  );
104
116
 
@@ -110,7 +122,10 @@ describe("Go Plugin", () => {
110
122
  `go build -ldflags="-s -w" -o .bin/testFunc5 functions/func5/main.go`,
111
123
  {
112
124
  cwd: ".",
113
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
125
+ env: {
126
+ ...process.env,
127
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
128
+ },
114
129
  },
115
130
  );
116
131
 
@@ -122,7 +137,10 @@ describe("Go Plugin", () => {
122
137
  `go build -ldflags="-s -w" -o .bin/testFunc6 functions/func6`,
123
138
  {
124
139
  cwd: ".",
125
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
140
+ env: {
141
+ ...process.env,
142
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
143
+ },
126
144
  },
127
145
  );
128
146
  });
@@ -155,7 +173,10 @@ describe("Go Plugin", () => {
155
173
  `go build -o .bin/testFunc1 functions/func1/main.go`,
156
174
  {
157
175
  cwd: ".",
158
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
176
+ env: {
177
+ ...process.env,
178
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
179
+ },
159
180
  },
160
181
  );
161
182
  });
@@ -222,7 +243,10 @@ describe("Go Plugin", () => {
222
243
  `go build -ldflags="-s -w" -o ../.bin/testFunc1 functions/func1/main.go`,
223
244
  {
224
245
  cwd: "gopath",
225
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
246
+ env: {
247
+ ...process.env,
248
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
249
+ },
226
250
  },
227
251
  );
228
252
  });
@@ -323,7 +347,10 @@ describe("Go Plugin", () => {
323
347
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
324
348
  {
325
349
  cwd: ".",
326
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
350
+ env: {
351
+ ...process.env,
352
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
353
+ },
327
354
  },
328
355
  );
329
356
  });
@@ -365,7 +392,10 @@ describe("Go Plugin", () => {
365
392
  `go build -ldflags="-s -w" -o ../../.bin/testFunc1 .`,
366
393
  {
367
394
  cwd: "functions/func1",
368
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
395
+ env: {
396
+ ...process.env,
397
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
398
+ },
369
399
  },
370
400
  );
371
401
 
@@ -377,7 +407,10 @@ describe("Go Plugin", () => {
377
407
  `go build -ldflags="-s -w" -o ../../.bin/testFunc2 main.go`,
378
408
  {
379
409
  cwd: "functions/func2",
380
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
410
+ env: {
411
+ ...process.env,
412
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
413
+ },
381
414
  },
382
415
  );
383
416
  });
@@ -503,14 +536,20 @@ describe("Go Plugin", () => {
503
536
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
504
537
  {
505
538
  cwd: ".",
506
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
539
+ env: {
540
+ ...process.env,
541
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
542
+ },
507
543
  },
508
544
  );
509
545
  expect(execStub).to.have.been.calledWith(
510
546
  `go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
511
547
  {
512
548
  cwd: ".",
513
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
549
+ env: {
550
+ ...process.env,
551
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
552
+ },
514
553
  },
515
554
  );
516
555
 
@@ -557,7 +596,10 @@ describe("Go Plugin", () => {
557
596
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
558
597
  {
559
598
  cwd: ".",
560
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
599
+ env: {
600
+ ...process.env,
601
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
602
+ },
561
603
  },
562
604
  );
563
605
 
@@ -604,11 +646,62 @@ describe("Go Plugin", () => {
604
646
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
605
647
  {
606
648
  cwd: ".",
607
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
649
+ env: {
650
+ ...process.env,
651
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
652
+ },
608
653
  },
609
654
  );
610
655
 
611
656
  // Total calls should be 3: 2 beforeBuild commands + 1 compilation command
612
657
  expect(execStub.callCount).to.equal(3);
613
658
  });
659
+ it("Does not leak arm64 GOARCH into x86_64 functions", async () => {
660
+ config.service.setFunctions({
661
+ armFunc: {
662
+ name: "armFunc",
663
+ runtime: "provided.al2023",
664
+ architecture: "arm64",
665
+ handler: "functions/arm",
666
+ },
667
+ x86Func: {
668
+ name: "x86Func",
669
+ runtime: "provided.al2023",
670
+ handler: "functions/x86",
671
+ },
672
+ });
673
+
674
+ const plugin = new Go(config);
675
+ plugin.exec = execStub;
676
+ plugin.readFileSync = readFileSyncStub;
677
+ plugin.zip = admZipStub;
678
+
679
+ await plugin.compileFunctions();
680
+
681
+ const envOf = (bin) =>
682
+ execStub.getCalls().find((c) => c.args[0].includes(bin)).args[1].env;
683
+ expect(envOf(".bin/armFunc").GOARCH).to.equal("arm64");
684
+ expect(envOf(".bin/x86Func").GOARCH).to.equal("amd64");
685
+ expect(plugin.config.env.GOARCH).to.equal(undefined);
686
+ });
687
+
688
+ it("Produces byte-identical zips for identical binaries", async () => {
689
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sp-go-"));
690
+ const bin = path.join(dir, "fn");
691
+ fs.writeFileSync(bin, "fake binary content");
692
+ config.service.setFunctions({ fn: { name: "fn", handler: "fn" } });
693
+
694
+ const plugin = new Go(config);
695
+ plugin.zip = new AdmZip();
696
+
697
+ plugin.packageBootstrap("fn", bin);
698
+ const first = fs.readFileSync(bin + ".zip");
699
+ await new Promise((r) => setTimeout(r, 2100)); // DOS mtime has 2s resolution
700
+ plugin.zip = new AdmZip();
701
+ plugin.packageBootstrap("fn", bin);
702
+ const second = fs.readFileSync(bin + ".zip");
703
+
704
+ fs.rmSync(dir, { recursive: true });
705
+ expect(first.equals(second)).to.equal(true);
706
+ }).timeout(5000);
614
707
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-plugin-go",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Serverless Framework plugin that compiles Go functions on the fly",
6
6
  "main": "index.js",
@@ -54,7 +54,7 @@
54
54
  "eslint-plugin-import": "^2.29.0",
55
55
  "eslint-plugin-prettier": "^5.0.1",
56
56
  "fs": "^0.0.1-security",
57
- "mocha": "^10.2.0",
57
+ "mocha": "^12.0.3",
58
58
  "prettier": "^3.0.3",
59
59
  "sinon": "^17.0.0",
60
60
  "sinon-chai": "^3.7.0"