serverless-plugin-go 0.1.1 → 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.1"
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,21 @@
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
+
13
+ ## v0.1.2 (2026-03-31)
14
+
15
+
16
+ - chore: add support for provided.al2023 runtime (#11)
17
+ - Co-authored-by: Ariel Santos <ariel.santos@draftea.com>
18
+
1
19
  ## v0.1.1 (2026-01-21)
2
20
 
3
21
 
package/README.md CHANGED
@@ -31,7 +31,7 @@
31
31
  ```yaml
32
32
  functions:
33
33
  example:
34
- runtime: 'provided.al2'
34
+ runtime: 'provided.al2023' # or provided.al2
35
35
  handler: 'functions/example/main.go' # or just functions/example
36
36
  ```
37
37
 
@@ -87,7 +87,7 @@ Change architecture field from `provider` or function configuration to `arm64`:
87
87
  ```yaml
88
88
  provider:
89
89
  architecture: 'arm64'
90
- runtime: 'provided.al2'
90
+ runtime: 'provided.al2023' # or provided.al2
91
91
 
92
92
  # If you define architecture at function level, this will have preference instead the provider configuration
93
93
 
package/index.js CHANGED
@@ -8,10 +8,13 @@ import AdmZip from "adm-zip";
8
8
  import { readFileSync } from "fs";
9
9
 
10
10
  // SupportedRuntimes Runtimes that are allowed to run Golang lambdas.
11
- const supportedRuntimes = ["provided.al2"];
11
+ 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
 
@@ -57,6 +63,16 @@ describe("Go Plugin", () => {
57
63
  runtime: "go1.x",
58
64
  handler: "functions/func4",
59
65
  },
66
+ testFunc5: {
67
+ name: "testFunc5",
68
+ runtime: "provided.al2023",
69
+ handler: "functions/func5/main.go",
70
+ },
71
+ testFunc6: {
72
+ name: "testFunc6",
73
+ runtime: "provided.al2023",
74
+ handler: "functions/func6",
75
+ },
60
76
  });
61
77
 
62
78
  const plugin = new Go(config);
@@ -76,7 +92,10 @@ describe("Go Plugin", () => {
76
92
  `go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
77
93
  {
78
94
  cwd: ".",
79
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
95
+ env: {
96
+ ...process.env,
97
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
98
+ },
80
99
  },
81
100
  );
82
101
 
@@ -88,7 +107,40 @@ describe("Go Plugin", () => {
88
107
  `go build -ldflags="-s -w" -o .bin/testFunc3 functions/func3`,
89
108
  {
90
109
  cwd: ".",
91
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
110
+ env: {
111
+ ...process.env,
112
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
113
+ },
114
+ },
115
+ );
116
+
117
+ expect(config.service.functions.testFunc5.handler).to.equal(
118
+ `.bin/testFunc5`,
119
+ );
120
+
121
+ expect(execStub).to.have.been.calledWith(
122
+ `go build -ldflags="-s -w" -o .bin/testFunc5 functions/func5/main.go`,
123
+ {
124
+ cwd: ".",
125
+ env: {
126
+ ...process.env,
127
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
128
+ },
129
+ },
130
+ );
131
+
132
+ expect(config.service.functions.testFunc6.handler).to.equal(
133
+ `.bin/testFunc6`,
134
+ );
135
+
136
+ expect(execStub).to.have.been.calledWith(
137
+ `go build -ldflags="-s -w" -o .bin/testFunc6 functions/func6`,
138
+ {
139
+ cwd: ".",
140
+ env: {
141
+ ...process.env,
142
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
143
+ },
92
144
  },
93
145
  );
94
146
  });
@@ -121,7 +173,10 @@ describe("Go Plugin", () => {
121
173
  `go build -o .bin/testFunc1 functions/func1/main.go`,
122
174
  {
123
175
  cwd: ".",
124
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
176
+ env: {
177
+ ...process.env,
178
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
179
+ },
125
180
  },
126
181
  );
127
182
  });
@@ -188,7 +243,10 @@ describe("Go Plugin", () => {
188
243
  `go build -ldflags="-s -w" -o ../.bin/testFunc1 functions/func1/main.go`,
189
244
  {
190
245
  cwd: "gopath",
191
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
246
+ env: {
247
+ ...process.env,
248
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
249
+ },
192
250
  },
193
251
  );
194
252
  });
@@ -243,6 +301,30 @@ describe("Go Plugin", () => {
243
301
  expect(execStub).to.have.been.calledOnce;
244
302
  });
245
303
 
304
+ it("Compiles Go function with global runtime provided.al2023 defined", async () => {
305
+ config.service
306
+ .setProvider({
307
+ runtime: "provided.al2023",
308
+ })
309
+ .setFunctions({
310
+ testFunc1: {
311
+ name: "testFunc1",
312
+ handler: "functions/func1/main.go",
313
+ },
314
+ });
315
+
316
+ const plugin = new Go(config);
317
+ plugin.exec = execStub;
318
+ plugin.readFileSync = readFileSyncStub;
319
+ plugin.zip = admZipStub;
320
+
321
+ // when
322
+ await plugin.hooks["before:package:createDeploymentArtifacts"]();
323
+
324
+ // then
325
+ expect(execStub).to.have.been.calledOnce;
326
+ });
327
+
246
328
  it("Compiles single Go function", async () => {
247
329
  config.service.setFunctions({
248
330
  testFunc1: {
@@ -265,7 +347,10 @@ describe("Go Plugin", () => {
265
347
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
266
348
  {
267
349
  cwd: ".",
268
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
350
+ env: {
351
+ ...process.env,
352
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
353
+ },
269
354
  },
270
355
  );
271
356
  });
@@ -307,7 +392,10 @@ describe("Go Plugin", () => {
307
392
  `go build -ldflags="-s -w" -o ../../.bin/testFunc1 .`,
308
393
  {
309
394
  cwd: "functions/func1",
310
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
395
+ env: {
396
+ ...process.env,
397
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
398
+ },
311
399
  },
312
400
  );
313
401
 
@@ -319,7 +407,10 @@ describe("Go Plugin", () => {
319
407
  `go build -ldflags="-s -w" -o ../../.bin/testFunc2 main.go`,
320
408
  {
321
409
  cwd: "functions/func2",
322
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
410
+ env: {
411
+ ...process.env,
412
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
413
+ },
323
414
  },
324
415
  );
325
416
  });
@@ -445,14 +536,20 @@ describe("Go Plugin", () => {
445
536
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
446
537
  {
447
538
  cwd: ".",
448
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
539
+ env: {
540
+ ...process.env,
541
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
542
+ },
449
543
  },
450
544
  );
451
545
  expect(execStub).to.have.been.calledWith(
452
546
  `go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
453
547
  {
454
548
  cwd: ".",
455
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
549
+ env: {
550
+ ...process.env,
551
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
552
+ },
456
553
  },
457
554
  );
458
555
 
@@ -499,7 +596,10 @@ describe("Go Plugin", () => {
499
596
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
500
597
  {
501
598
  cwd: ".",
502
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
599
+ env: {
600
+ ...process.env,
601
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
602
+ },
503
603
  },
504
604
  );
505
605
 
@@ -546,11 +646,62 @@ describe("Go Plugin", () => {
546
646
  `go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
547
647
  {
548
648
  cwd: ".",
549
- env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
649
+ env: {
650
+ ...process.env,
651
+ ...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
652
+ },
550
653
  },
551
654
  );
552
655
 
553
656
  // Total calls should be 3: 2 beforeBuild commands + 1 compilation command
554
657
  expect(execStub.callCount).to.equal(3);
555
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);
556
707
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-plugin-go",
3
- "version": "0.1.1",
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"