@reteps/dockerfmt 0.3.6 → 0.5.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@reteps/dockerfmt`
2
2
 
3
- Bindings around the Golang `dockerfmt` tooling. It uses [tinygo](https://github.com/tinygo-org/tinygo) to compile the Go code to WebAssembly, which is then used in the JS bindings.
3
+ Bindings around the Golang `dockerfmt` tooling. It compiles the Go code to WebAssembly (using standard Go's `GOOS=js GOARCH=wasm` target), which is then used in the JS bindings.
4
4
 
5
5
 
6
6
  ```js
package/dist/format.js CHANGED
@@ -1,29 +1,19 @@
1
1
  import './wasm_exec.js';
2
2
  export const formatDockerfileContents = async (fileContents, options, getWasm) => {
3
3
  const go = new Go(); // Defined in wasm_exec.js
4
- const encoder = new TextEncoder();
5
- const decoder = new TextDecoder();
6
- // get current working directory
7
4
  const wasmBuffer = await getWasm();
8
5
  const wasm = await WebAssembly.instantiate(wasmBuffer, go.importObject);
9
6
  /**
10
7
  * Do not await this promise, because it only resolves once the go main()
11
8
  * function has exited. But we need the main function to stay alive to be
12
- * able to call the `parse` and `print` function.
9
+ * able to call the formatBytes function.
13
10
  */
14
11
  go.run(wasm.instance);
15
- const { memory, malloc, free, formatBytes } = wasm.instance.exports;
16
- const fileBufferBytes = encoder.encode(fileContents);
17
- const filePointer = malloc(fileBufferBytes.byteLength);
18
- new Uint8Array(memory.buffer).set(fileBufferBytes, filePointer);
19
- // Call formatBytes function from WebAssembly
20
- const resultPointer = formatBytes(filePointer, fileBufferBytes.byteLength, options.indent, options.trailingNewline, options.spaceRedirects);
21
- // Decode the result
22
- const resultBytes = new Uint8Array(memory.buffer).subarray(resultPointer);
23
- const end = resultBytes.indexOf(0);
24
- const result = decoder.decode(resultBytes.subarray(0, end));
25
- free(filePointer);
26
- return result;
12
+ const formatBytes = globalThis.__dockerfmt_formatBytes;
13
+ if (typeof formatBytes !== 'function') {
14
+ throw new Error('dockerfmt WASM module did not register formatBytes');
15
+ }
16
+ return formatBytes(fileContents, options.indent, options.trailingNewline, options.spaceRedirects);
27
17
  };
28
18
  export const formatDockerfile = () => {
29
19
  throw new Error('`formatDockerfile` is not implemented in the browser. Use `formatDockerfileContents` instead.');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { formatDockerfileContents } from './node.js';
4
+ const defaultOptions = {
5
+ indent: 4,
6
+ trailingNewline: true,
7
+ spaceRedirects: false,
8
+ };
9
+ describe('formatDockerfileContents', () => {
10
+ it('formats a basic Dockerfile', async () => {
11
+ const input = `from alpine
12
+ run echo hello
13
+ `.trim();
14
+ const result = await formatDockerfileContents(input, defaultOptions);
15
+ assert.equal(result, 'FROM alpine\nRUN echo hello\n');
16
+ });
17
+ it('formats CMD JSON form with spaces', async () => {
18
+ const input = `FROM alpine
19
+ CMD ["ls","-la"]
20
+ `.trim();
21
+ const result = await formatDockerfileContents(input, defaultOptions);
22
+ assert.equal(result, 'FROM alpine\nCMD ["ls", "-la"]\n');
23
+ });
24
+ it('formats RUN JSON form with spaces', async () => {
25
+ const input = `FROM alpine
26
+ RUN ["echo","hello"]
27
+ `.trim();
28
+ const result = await formatDockerfileContents(input, defaultOptions);
29
+ assert.equal(result, 'FROM alpine\nRUN ["echo", "hello"]\n');
30
+ });
31
+ it('handles the issue #25 reproduction case', async () => {
32
+ const input = `
33
+ FROM nginx
34
+ WORKDIR /app
35
+ ARG PROJECT_DIR=/
36
+ ARG NGINX_CONF=nginx.conf
37
+ COPY $NGINX_CONF /etc/nginx/conf.d/nginx.conf
38
+ COPY $PROJECT_DIR /app
39
+ CMD mkdir --parents /var/log/nginx && nginx -g "daemon off;"
40
+ `.trim();
41
+ const result = await formatDockerfileContents(input, {
42
+ indent: 4,
43
+ spaceRedirects: false,
44
+ trailingNewline: true,
45
+ });
46
+ assert.ok(result.includes('FROM nginx'));
47
+ assert.ok(result.includes('WORKDIR /app'));
48
+ assert.ok(result.endsWith('\n'));
49
+ });
50
+ it('respects trailingNewline: false', async () => {
51
+ const input = 'FROM alpine';
52
+ const result = await formatDockerfileContents(input, {
53
+ ...defaultOptions,
54
+ trailingNewline: false,
55
+ });
56
+ assert.ok(!result.endsWith('\n'));
57
+ });
58
+ it('respects indent option', async () => {
59
+ const input = `FROM alpine
60
+ RUN echo a \\
61
+ && echo b
62
+ `.trim();
63
+ const result = await formatDockerfileContents(input, {
64
+ ...defaultOptions,
65
+ indent: 2,
66
+ });
67
+ assert.ok(result.includes(' && echo b'));
68
+ });
69
+ });
package/dist/format.wasm CHANGED
Binary file
package/dist/wasm_exec.js CHANGED
@@ -1,54 +1,26 @@
1
- // https://github.com/tinygo-org/tinygo/blob/64d8a043084cb9a56763192000e40ddc5dce733f/targets/wasm_exec.js
2
1
  // Copyright 2018 The Go Authors. All rights reserved.
3
2
  // Use of this source code is governed by a BSD-style
4
3
  // license that can be found in the LICENSE file.
5
- //
6
- // This file has been modified for use by the TinyGo compiler.
7
4
 
8
- (() => {
9
- // Map multiple JavaScript environments to a single common API,
10
- // preferring web standards over Node.js API.
11
- //
12
- // Environments considered:
13
- // - Browsers
14
- // - Node.js
15
- // - Electron
16
- // - Parcel
17
-
18
- if (typeof global !== "undefined") {
19
- // global already exists
20
- } else if (typeof window !== "undefined") {
21
- window.global = window;
22
- } else if (typeof self !== "undefined") {
23
- self.global = self;
24
- } else {
25
- throw new Error("cannot export Go (neither global, window nor self is defined)");
26
- }
27
-
28
- if (!global.require && typeof require !== "undefined") {
29
- global.require = require;
30
- }
31
-
32
- if (!global.fs && global.require) {
33
- global.fs = require("node:fs");
34
- }
5
+ "use strict";
35
6
 
7
+ (() => {
36
8
  const enosys = () => {
37
9
  const err = new Error("not implemented");
38
10
  err.code = "ENOSYS";
39
11
  return err;
40
12
  };
41
13
 
42
- if (!global.fs) {
14
+ if (!globalThis.fs) {
43
15
  let outputBuf = "";
44
- global.fs = {
45
- constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
16
+ globalThis.fs = {
17
+ constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
46
18
  writeSync(fd, buf) {
47
19
  outputBuf += decoder.decode(buf);
48
20
  const nl = outputBuf.lastIndexOf("\n");
49
21
  if (nl != -1) {
50
- console.log(outputBuf.substr(0, nl));
51
- outputBuf = outputBuf.substr(nl + 1);
22
+ console.log(outputBuf.substring(0, nl));
23
+ outputBuf = outputBuf.substring(nl + 1);
52
24
  }
53
25
  return buf.length;
54
26
  },
@@ -86,8 +58,8 @@
86
58
  };
87
59
  }
88
60
 
89
- if (!global.process) {
90
- global.process = {
61
+ if (!globalThis.process) {
62
+ globalThis.process = {
91
63
  getuid() { return -1; },
92
64
  getgid() { return -1; },
93
65
  geteuid() { return -1; },
@@ -101,53 +73,66 @@
101
73
  }
102
74
  }
103
75
 
104
- if (!global.crypto) {
105
- const nodeCrypto = require("node:crypto");
106
- global.crypto = {
107
- getRandomValues(b) {
108
- nodeCrypto.randomFillSync(b);
109
- },
110
- };
76
+ if (!globalThis.path) {
77
+ globalThis.path = {
78
+ resolve(...pathSegments) {
79
+ return pathSegments.join("/");
80
+ }
81
+ }
111
82
  }
112
83
 
113
- if (!global.performance) {
114
- global.performance = {
115
- now() {
116
- const [sec, nsec] = process.hrtime();
117
- return sec * 1000 + nsec / 1000000;
118
- },
119
- };
84
+ if (!globalThis.crypto) {
85
+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
120
86
  }
121
87
 
122
- if (!global.TextEncoder) {
123
- global.TextEncoder = require("node:util").TextEncoder;
88
+ if (!globalThis.performance) {
89
+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
124
90
  }
125
91
 
126
- if (!global.TextDecoder) {
127
- global.TextDecoder = require("node:util").TextDecoder;
92
+ if (!globalThis.TextEncoder) {
93
+ throw new Error("globalThis.TextEncoder is not available, polyfill required");
128
94
  }
129
95
 
130
- // End of polyfills for common API.
96
+ if (!globalThis.TextDecoder) {
97
+ throw new Error("globalThis.TextDecoder is not available, polyfill required");
98
+ }
131
99
 
132
100
  const encoder = new TextEncoder("utf-8");
133
101
  const decoder = new TextDecoder("utf-8");
134
- let reinterpretBuf = new DataView(new ArrayBuffer(8));
135
- var logLine = [];
136
- const wasmExit = {}; // thrown to exit via proc_exit (not an error)
137
102
 
138
- global.Go = class {
103
+ globalThis.Go = class {
139
104
  constructor() {
140
- this._callbackTimeouts = new Map();
105
+ this.argv = ["js"];
106
+ this.env = {};
107
+ this.exit = (code) => {
108
+ if (code !== 0) {
109
+ console.warn("exit code:", code);
110
+ }
111
+ };
112
+ this._exitPromise = new Promise((resolve) => {
113
+ this._resolveExitPromise = resolve;
114
+ });
115
+ this._pendingEvent = null;
116
+ this._scheduledTimeouts = new Map();
141
117
  this._nextCallbackTimeoutID = 1;
142
118
 
143
- const mem = () => {
144
- // The buffer may change when requesting more memory.
145
- return new DataView(this._inst.exports.memory.buffer);
119
+ const setInt64 = (addr, v) => {
120
+ this.mem.setUint32(addr + 0, v, true);
121
+ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
146
122
  }
147
123
 
148
- const unboxValue = (v_ref) => {
149
- reinterpretBuf.setBigInt64(0, v_ref, true);
150
- const f = reinterpretBuf.getFloat64(0, true);
124
+ const setInt32 = (addr, v) => {
125
+ this.mem.setUint32(addr + 0, v, true);
126
+ }
127
+
128
+ const getInt64 = (addr) => {
129
+ const low = this.mem.getUint32(addr + 0, true);
130
+ const high = this.mem.getInt32(addr + 4, true);
131
+ return low + high * 4294967296;
132
+ }
133
+
134
+ const loadValue = (addr) => {
135
+ const f = this.mem.getFloat64(addr, true);
151
136
  if (f === 0) {
152
137
  return undefined;
153
138
  }
@@ -155,77 +140,69 @@
155
140
  return f;
156
141
  }
157
142
 
158
- const id = v_ref & 0xffffffffn;
143
+ const id = this.mem.getUint32(addr, true);
159
144
  return this._values[id];
160
145
  }
161
146
 
147
+ const storeValue = (addr, v) => {
148
+ const nanHead = 0x7FF80000;
162
149
 
163
- const loadValue = (addr) => {
164
- let v_ref = mem().getBigUint64(addr, true);
165
- return unboxValue(v_ref);
166
- }
167
-
168
- const boxValue = (v) => {
169
- const nanHead = 0x7FF80000n;
170
-
171
- if (typeof v === "number") {
150
+ if (typeof v === "number" && v !== 0) {
172
151
  if (isNaN(v)) {
173
- return nanHead << 32n;
174
- }
175
- if (v === 0) {
176
- return (nanHead << 32n) | 1n;
152
+ this.mem.setUint32(addr + 4, nanHead, true);
153
+ this.mem.setUint32(addr, 0, true);
154
+ return;
177
155
  }
178
- reinterpretBuf.setFloat64(0, v, true);
179
- return reinterpretBuf.getBigInt64(0, true);
156
+ this.mem.setFloat64(addr, v, true);
157
+ return;
180
158
  }
181
159
 
182
- switch (v) {
183
- case undefined:
184
- return 0n;
185
- case null:
186
- return (nanHead << 32n) | 2n;
187
- case true:
188
- return (nanHead << 32n) | 3n;
189
- case false:
190
- return (nanHead << 32n) | 4n;
160
+ if (v === undefined) {
161
+ this.mem.setFloat64(addr, 0, true);
162
+ return;
191
163
  }
192
164
 
193
165
  let id = this._ids.get(v);
194
166
  if (id === undefined) {
195
167
  id = this._idPool.pop();
196
168
  if (id === undefined) {
197
- id = BigInt(this._values.length);
169
+ id = this._values.length;
198
170
  }
199
171
  this._values[id] = v;
200
172
  this._goRefCounts[id] = 0;
201
173
  this._ids.set(v, id);
202
174
  }
203
175
  this._goRefCounts[id]++;
204
- let typeFlag = 1n;
176
+ let typeFlag = 0;
205
177
  switch (typeof v) {
178
+ case "object":
179
+ if (v !== null) {
180
+ typeFlag = 1;
181
+ }
182
+ break;
206
183
  case "string":
207
- typeFlag = 2n;
184
+ typeFlag = 2;
208
185
  break;
209
186
  case "symbol":
210
- typeFlag = 3n;
187
+ typeFlag = 3;
211
188
  break;
212
189
  case "function":
213
- typeFlag = 4n;
190
+ typeFlag = 4;
214
191
  break;
215
192
  }
216
- return id | ((nanHead | typeFlag) << 32n);
217
- }
218
-
219
- const storeValue = (addr, v) => {
220
- let v_ref = boxValue(v);
221
- mem().setBigUint64(addr, v_ref, true);
193
+ this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
194
+ this.mem.setUint32(addr, id, true);
222
195
  }
223
196
 
224
- const loadSlice = (array, len, cap) => {
225
- return new Uint8Array(this._inst.exports.memory.buffer, array, len);
197
+ const loadSlice = (addr) => {
198
+ const array = getInt64(addr + 0);
199
+ const len = getInt64(addr + 8);
200
+ return new Uint8Array(this._inst.exports.mem.buffer, array, len);
226
201
  }
227
202
 
228
- const loadSliceOfValues = (array, len, cap) => {
203
+ const loadSliceOfValues = (addr) => {
204
+ const array = getInt64(addr + 0);
205
+ const len = getInt64(addr + 8);
229
206
  const a = new Array(len);
230
207
  for (let i = 0; i < len; i++) {
231
208
  a[i] = loadValue(array + i * 8);
@@ -233,287 +210,353 @@
233
210
  return a;
234
211
  }
235
212
 
236
- const loadString = (ptr, len) => {
237
- return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
213
+ const loadString = (addr) => {
214
+ const saddr = getInt64(addr + 0);
215
+ const len = getInt64(addr + 8);
216
+ return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
217
+ }
218
+
219
+ const testCallExport = (a, b) => {
220
+ this._inst.exports.testExport0();
221
+ return this._inst.exports.testExport(a, b);
238
222
  }
239
223
 
240
224
  const timeOrigin = Date.now() - performance.now();
241
225
  this.importObject = {
242
- wasi_snapshot_preview1: {
243
- // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
244
- fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
245
- let nwritten = 0;
246
- if (fd == 1) {
247
- for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
248
- let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
249
- let ptr = mem().getUint32(iov_ptr + 0, true);
250
- let len = mem().getUint32(iov_ptr + 4, true);
251
- nwritten += len;
252
- for (let i=0; i<len; i++) {
253
- let c = mem().getUint8(ptr+i);
254
- if (c == 13) { // CR
255
- // ignore
256
- } else if (c == 10) { // LF
257
- // write line
258
- let line = decoder.decode(new Uint8Array(logLine));
259
- logLine = [];
260
- console.log(line);
261
- } else {
262
- logLine.push(c);
263
- }
264
- }
265
- }
266
- } else {
267
- console.error('invalid file descriptor:', fd);
268
- }
269
- mem().setUint32(nwritten_ptr, nwritten, true);
270
- return 0;
271
- },
272
- fd_close: () => 0, // dummy
273
- fd_fdstat_get: () => 0, // dummy
274
- fd_seek: () => 0, // dummy
275
- proc_exit: (code) => {
226
+ _gotest: {
227
+ add: (a, b) => a + b,
228
+ callExport: testCallExport,
229
+ },
230
+ gojs: {
231
+ // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
232
+ // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
233
+ // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
234
+ // This changes the SP, thus we have to update the SP used by the imported function.
235
+
236
+ // func wasmExit(code int32)
237
+ "runtime.wasmExit": (sp) => {
238
+ sp >>>= 0;
239
+ const code = this.mem.getInt32(sp + 8, true);
276
240
  this.exited = true;
277
- this.exitCode = code;
278
- this._resolveExitPromise();
279
- throw wasmExit;
241
+ delete this._inst;
242
+ delete this._values;
243
+ delete this._goRefCounts;
244
+ delete this._ids;
245
+ delete this._idPool;
246
+ this.exit(code);
280
247
  },
281
- random_get: (bufPtr, bufLen) => {
282
- crypto.getRandomValues(loadSlice(bufPtr, bufLen));
283
- return 0;
248
+
249
+ // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
250
+ "runtime.wasmWrite": (sp) => {
251
+ sp >>>= 0;
252
+ const fd = getInt64(sp + 8);
253
+ const p = getInt64(sp + 16);
254
+ const n = this.mem.getInt32(sp + 24, true);
255
+ fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
284
256
  },
285
- },
286
- gojs: {
287
- // func ticks() float64
288
- "runtime.ticks": () => {
289
- return timeOrigin + performance.now();
257
+
258
+ // func resetMemoryDataView()
259
+ "runtime.resetMemoryDataView": (sp) => {
260
+ sp >>>= 0;
261
+ this.mem = new DataView(this._inst.exports.mem.buffer);
262
+ },
263
+
264
+ // func nanotime1() int64
265
+ "runtime.nanotime1": (sp) => {
266
+ sp >>>= 0;
267
+ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
268
+ },
269
+
270
+ // func walltime() (sec int64, nsec int32)
271
+ "runtime.walltime": (sp) => {
272
+ sp >>>= 0;
273
+ const msec = (new Date).getTime();
274
+ setInt64(sp + 8, msec / 1000);
275
+ this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
290
276
  },
291
277
 
292
- // func sleepTicks(timeout float64)
293
- "runtime.sleepTicks": (timeout) => {
294
- // Do not sleep, only reactivate scheduler after the given timeout.
295
- setTimeout(() => {
296
- if (this.exited) return;
297
- try {
298
- this._inst.exports.go_scheduler();
299
- } catch (e) {
300
- if (e !== wasmExit) throw e;
301
- }
302
- }, timeout);
278
+ // func scheduleTimeoutEvent(delay int64) int32
279
+ "runtime.scheduleTimeoutEvent": (sp) => {
280
+ sp >>>= 0;
281
+ const id = this._nextCallbackTimeoutID;
282
+ this._nextCallbackTimeoutID++;
283
+ this._scheduledTimeouts.set(id, setTimeout(
284
+ () => {
285
+ this._resume();
286
+ while (this._scheduledTimeouts.has(id)) {
287
+ // for some reason Go failed to register the timeout event, log and try again
288
+ // (temporary workaround for https://github.com/golang/go/issues/28975)
289
+ console.warn("scheduleTimeoutEvent: missed timeout event");
290
+ this._resume();
291
+ }
292
+ },
293
+ getInt64(sp + 8),
294
+ ));
295
+ this.mem.setInt32(sp + 16, id, true);
296
+ },
297
+
298
+ // func clearTimeoutEvent(id int32)
299
+ "runtime.clearTimeoutEvent": (sp) => {
300
+ sp >>>= 0;
301
+ const id = this.mem.getInt32(sp + 8, true);
302
+ clearTimeout(this._scheduledTimeouts.get(id));
303
+ this._scheduledTimeouts.delete(id);
304
+ },
305
+
306
+ // func getRandomData(r []byte)
307
+ "runtime.getRandomData": (sp) => {
308
+ sp >>>= 0;
309
+ crypto.getRandomValues(loadSlice(sp + 8));
303
310
  },
304
311
 
305
312
  // func finalizeRef(v ref)
306
- "syscall/js.finalizeRef": (v_ref) => {
307
- // Note: TinyGo does not support finalizers so this is only called
308
- // for one specific case, by js.go:jsString. and can/might leak memory.
309
- const id = v_ref & 0xffffffffn;
310
- if (this._goRefCounts?.[id] !== undefined) {
311
- this._goRefCounts[id]--;
312
- if (this._goRefCounts[id] === 0) {
313
- const v = this._values[id];
314
- this._values[id] = null;
315
- this._ids.delete(v);
316
- this._idPool.push(id);
317
- }
318
- } else {
319
- console.error("syscall/js.finalizeRef: unknown id", id);
313
+ "syscall/js.finalizeRef": (sp) => {
314
+ sp >>>= 0;
315
+ const id = this.mem.getUint32(sp + 8, true);
316
+ this._goRefCounts[id]--;
317
+ if (this._goRefCounts[id] === 0) {
318
+ const v = this._values[id];
319
+ this._values[id] = null;
320
+ this._ids.delete(v);
321
+ this._idPool.push(id);
320
322
  }
321
323
  },
322
324
 
323
325
  // func stringVal(value string) ref
324
- "syscall/js.stringVal": (value_ptr, value_len) => {
325
- value_ptr >>>= 0;
326
- const s = loadString(value_ptr, value_len);
327
- return boxValue(s);
326
+ "syscall/js.stringVal": (sp) => {
327
+ sp >>>= 0;
328
+ storeValue(sp + 24, loadString(sp + 8));
328
329
  },
329
330
 
330
331
  // func valueGet(v ref, p string) ref
331
- "syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
332
- let prop = loadString(p_ptr, p_len);
333
- let v = unboxValue(v_ref);
334
- let result = Reflect.get(v, prop);
335
- return boxValue(result);
332
+ "syscall/js.valueGet": (sp) => {
333
+ sp >>>= 0;
334
+ const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
335
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
336
+ storeValue(sp + 32, result);
336
337
  },
337
338
 
338
339
  // func valueSet(v ref, p string, x ref)
339
- "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
340
- const v = unboxValue(v_ref);
341
- const p = loadString(p_ptr, p_len);
342
- const x = unboxValue(x_ref);
343
- Reflect.set(v, p, x);
340
+ "syscall/js.valueSet": (sp) => {
341
+ sp >>>= 0;
342
+ Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
344
343
  },
345
344
 
346
345
  // func valueDelete(v ref, p string)
347
- "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
348
- const v = unboxValue(v_ref);
349
- const p = loadString(p_ptr, p_len);
350
- Reflect.deleteProperty(v, p);
346
+ "syscall/js.valueDelete": (sp) => {
347
+ sp >>>= 0;
348
+ Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
351
349
  },
352
350
 
353
351
  // func valueIndex(v ref, i int) ref
354
- "syscall/js.valueIndex": (v_ref, i) => {
355
- return boxValue(Reflect.get(unboxValue(v_ref), i));
352
+ "syscall/js.valueIndex": (sp) => {
353
+ sp >>>= 0;
354
+ storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
356
355
  },
357
356
 
358
357
  // valueSetIndex(v ref, i int, x ref)
359
- "syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
360
- Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
358
+ "syscall/js.valueSetIndex": (sp) => {
359
+ sp >>>= 0;
360
+ Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
361
361
  },
362
362
 
363
363
  // func valueCall(v ref, m string, args []ref) (ref, bool)
364
- "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
365
- const v = unboxValue(v_ref);
366
- const name = loadString(m_ptr, m_len);
367
- const args = loadSliceOfValues(args_ptr, args_len, args_cap);
364
+ "syscall/js.valueCall": (sp) => {
365
+ sp >>>= 0;
368
366
  try {
369
- const m = Reflect.get(v, name);
370
- storeValue(ret_addr, Reflect.apply(m, v, args));
371
- mem().setUint8(ret_addr + 8, 1);
367
+ const v = loadValue(sp + 8);
368
+ const m = Reflect.get(v, loadString(sp + 16));
369
+ const args = loadSliceOfValues(sp + 32);
370
+ const result = Reflect.apply(m, v, args);
371
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
372
+ storeValue(sp + 56, result);
373
+ this.mem.setUint8(sp + 64, 1);
372
374
  } catch (err) {
373
- storeValue(ret_addr, err);
374
- mem().setUint8(ret_addr + 8, 0);
375
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
376
+ storeValue(sp + 56, err);
377
+ this.mem.setUint8(sp + 64, 0);
375
378
  }
376
379
  },
377
380
 
378
381
  // func valueInvoke(v ref, args []ref) (ref, bool)
379
- "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
382
+ "syscall/js.valueInvoke": (sp) => {
383
+ sp >>>= 0;
380
384
  try {
381
- const v = unboxValue(v_ref);
382
- const args = loadSliceOfValues(args_ptr, args_len, args_cap);
383
- storeValue(ret_addr, Reflect.apply(v, undefined, args));
384
- mem().setUint8(ret_addr + 8, 1);
385
+ const v = loadValue(sp + 8);
386
+ const args = loadSliceOfValues(sp + 16);
387
+ const result = Reflect.apply(v, undefined, args);
388
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
389
+ storeValue(sp + 40, result);
390
+ this.mem.setUint8(sp + 48, 1);
385
391
  } catch (err) {
386
- storeValue(ret_addr, err);
387
- mem().setUint8(ret_addr + 8, 0);
392
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
393
+ storeValue(sp + 40, err);
394
+ this.mem.setUint8(sp + 48, 0);
388
395
  }
389
396
  },
390
397
 
391
398
  // func valueNew(v ref, args []ref) (ref, bool)
392
- "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
393
- const v = unboxValue(v_ref);
394
- const args = loadSliceOfValues(args_ptr, args_len, args_cap);
399
+ "syscall/js.valueNew": (sp) => {
400
+ sp >>>= 0;
395
401
  try {
396
- storeValue(ret_addr, Reflect.construct(v, args));
397
- mem().setUint8(ret_addr + 8, 1);
402
+ const v = loadValue(sp + 8);
403
+ const args = loadSliceOfValues(sp + 16);
404
+ const result = Reflect.construct(v, args);
405
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
406
+ storeValue(sp + 40, result);
407
+ this.mem.setUint8(sp + 48, 1);
398
408
  } catch (err) {
399
- storeValue(ret_addr, err);
400
- mem().setUint8(ret_addr+ 8, 0);
409
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
410
+ storeValue(sp + 40, err);
411
+ this.mem.setUint8(sp + 48, 0);
401
412
  }
402
413
  },
403
414
 
404
415
  // func valueLength(v ref) int
405
- "syscall/js.valueLength": (v_ref) => {
406
- return unboxValue(v_ref).length;
416
+ "syscall/js.valueLength": (sp) => {
417
+ sp >>>= 0;
418
+ setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
407
419
  },
408
420
 
409
421
  // valuePrepareString(v ref) (ref, int)
410
- "syscall/js.valuePrepareString": (ret_addr, v_ref) => {
411
- const s = String(unboxValue(v_ref));
412
- const str = encoder.encode(s);
413
- storeValue(ret_addr, str);
414
- mem().setInt32(ret_addr + 8, str.length, true);
422
+ "syscall/js.valuePrepareString": (sp) => {
423
+ sp >>>= 0;
424
+ const str = encoder.encode(String(loadValue(sp + 8)));
425
+ storeValue(sp + 16, str);
426
+ setInt64(sp + 24, str.length);
415
427
  },
416
428
 
417
429
  // valueLoadString(v ref, b []byte)
418
- "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
419
- const str = unboxValue(v_ref);
420
- loadSlice(slice_ptr, slice_len, slice_cap).set(str);
430
+ "syscall/js.valueLoadString": (sp) => {
431
+ sp >>>= 0;
432
+ const str = loadValue(sp + 8);
433
+ loadSlice(sp + 16).set(str);
421
434
  },
422
435
 
423
436
  // func valueInstanceOf(v ref, t ref) bool
424
- "syscall/js.valueInstanceOf": (v_ref, t_ref) => {
425
- return unboxValue(v_ref) instanceof unboxValue(t_ref);
437
+ "syscall/js.valueInstanceOf": (sp) => {
438
+ sp >>>= 0;
439
+ this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
426
440
  },
427
441
 
428
442
  // func copyBytesToGo(dst []byte, src ref) (int, bool)
429
- "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
430
- let num_bytes_copied_addr = ret_addr;
431
- let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
432
-
433
- const dst = loadSlice(dest_addr, dest_len);
434
- const src = unboxValue(src_ref);
443
+ "syscall/js.copyBytesToGo": (sp) => {
444
+ sp >>>= 0;
445
+ const dst = loadSlice(sp + 8);
446
+ const src = loadValue(sp + 32);
435
447
  if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
436
- mem().setUint8(returned_status_addr, 0); // Return "not ok" status
448
+ this.mem.setUint8(sp + 48, 0);
437
449
  return;
438
450
  }
439
451
  const toCopy = src.subarray(0, dst.length);
440
452
  dst.set(toCopy);
441
- mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
442
- mem().setUint8(returned_status_addr, 1); // Return "ok" status
453
+ setInt64(sp + 40, toCopy.length);
454
+ this.mem.setUint8(sp + 48, 1);
443
455
  },
444
456
 
445
- // copyBytesToJS(dst ref, src []byte) (int, bool)
446
- // Originally copied from upstream Go project, then modified:
447
- // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
448
- "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
449
- let num_bytes_copied_addr = ret_addr;
450
- let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
451
-
452
- const dst = unboxValue(dst_ref);
453
- const src = loadSlice(src_addr, src_len);
457
+ // func copyBytesToJS(dst ref, src []byte) (int, bool)
458
+ "syscall/js.copyBytesToJS": (sp) => {
459
+ sp >>>= 0;
460
+ const dst = loadValue(sp + 8);
461
+ const src = loadSlice(sp + 16);
454
462
  if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
455
- mem().setUint8(returned_status_addr, 0); // Return "not ok" status
463
+ this.mem.setUint8(sp + 48, 0);
456
464
  return;
457
465
  }
458
466
  const toCopy = src.subarray(0, dst.length);
459
467
  dst.set(toCopy);
460
- mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
461
- mem().setUint8(returned_status_addr, 1); // Return "ok" status
468
+ setInt64(sp + 40, toCopy.length);
469
+ this.mem.setUint8(sp + 48, 1);
470
+ },
471
+
472
+ "debug": (value) => {
473
+ console.log(value);
462
474
  },
463
475
  }
464
476
  };
465
-
466
- // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
467
- // For compatibility, we use both as long as Go 1.20 is supported.
468
- this.importObject.env = this.importObject.gojs;
469
477
  }
470
478
 
471
479
  async run(instance) {
480
+ if (!(instance instanceof WebAssembly.Instance)) {
481
+ throw new Error("Go.run: WebAssembly.Instance expected");
482
+ }
472
483
  this._inst = instance;
484
+ this.mem = new DataView(this._inst.exports.mem.buffer);
473
485
  this._values = [ // JS values that Go currently has references to, indexed by reference id
474
486
  NaN,
475
487
  0,
476
488
  null,
477
489
  true,
478
490
  false,
479
- global,
491
+ globalThis,
480
492
  this,
481
493
  ];
482
- this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
483
- this._ids = new Map(); // mapping from JS values to reference ids
484
- this._idPool = []; // unused ids that have been garbage collected
485
- this.exited = false; // whether the Go program has exited
486
- this.exitCode = 0;
487
-
488
- if (this._inst.exports._start) {
489
- let exitPromise = new Promise((resolve, reject) => {
490
- this._resolveExitPromise = resolve;
491
- });
492
-
493
- // Run program, but catch the wasmExit exception that's thrown
494
- // to return back here.
495
- try {
496
- this._inst.exports._start();
497
- } catch (e) {
498
- if (e !== wasmExit) throw e;
494
+ this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
495
+ this._ids = new Map([ // mapping from JS values to reference ids
496
+ [0, 1],
497
+ [null, 2],
498
+ [true, 3],
499
+ [false, 4],
500
+ [globalThis, 5],
501
+ [this, 6],
502
+ ]);
503
+ this._idPool = []; // unused ids that have been garbage collected
504
+ this.exited = false; // whether the Go program has exited
505
+
506
+ // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
507
+ let offset = 4096;
508
+
509
+ const strPtr = (str) => {
510
+ const ptr = offset;
511
+ const bytes = encoder.encode(str + "\0");
512
+ new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
513
+ offset += bytes.length;
514
+ if (offset % 8 !== 0) {
515
+ offset += 8 - (offset % 8);
499
516
  }
517
+ return ptr;
518
+ };
519
+
520
+ const argc = this.argv.length;
521
+
522
+ const argvPtrs = [];
523
+ this.argv.forEach((arg) => {
524
+ argvPtrs.push(strPtr(arg));
525
+ });
526
+ argvPtrs.push(0);
527
+
528
+ const keys = Object.keys(this.env).sort();
529
+ keys.forEach((key) => {
530
+ argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
531
+ });
532
+ argvPtrs.push(0);
533
+
534
+ const argv = offset;
535
+ argvPtrs.forEach((ptr) => {
536
+ this.mem.setUint32(offset, ptr, true);
537
+ this.mem.setUint32(offset + 4, 0, true);
538
+ offset += 8;
539
+ });
540
+
541
+ // The linker guarantees global data starts from at least wasmMinDataAddr.
542
+ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
543
+ const wasmMinDataAddr = 4096 + 8192;
544
+ if (offset >= wasmMinDataAddr) {
545
+ throw new Error("total length of command line and environment variables exceeds limit");
546
+ }
500
547
 
501
- await exitPromise;
502
- return this.exitCode;
503
- } else {
504
- this._inst.exports._initialize();
548
+ this._inst.exports.run(argc, argv);
549
+ if (this.exited) {
550
+ this._resolveExitPromise();
505
551
  }
552
+ await this._exitPromise;
506
553
  }
507
554
 
508
555
  _resume() {
509
556
  if (this.exited) {
510
557
  throw new Error("Go program has already exited");
511
558
  }
512
- try {
513
- this._inst.exports.resume();
514
- } catch (e) {
515
- if (e !== wasmExit) throw e;
516
- }
559
+ this._inst.exports.resume();
517
560
  if (this.exited) {
518
561
  this._resolveExitPromise();
519
562
  }
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@reteps/dockerfmt",
3
- "version": "0.3.6",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "",
6
- "repository": "git+https://github.com/reteps/dockerfmt/tree/main/js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/reteps/dockerfmt.git",
9
+ "directory": "js"
10
+ },
7
11
  "author": "Peter Stenger <pete@stenger.io>",
8
12
  "license": "MIT",
9
13
  "engines": {
@@ -29,9 +33,8 @@
29
33
  "dist"
30
34
  ],
31
35
  "scripts": {
32
- "//": "Requires tinygo 0.38.0 or later",
33
36
  "build": "npm run build-go && npm run build-js",
34
- "build-go": "tinygo build -o format.wasm -target wasm --no-debug",
37
+ "build-go": "GOOS=js GOARCH=wasm go build -ldflags='-s -w' -o format.wasm && wasm-opt --enable-bulk-memory -Oz -o format-opt.wasm format.wasm && mv format-opt.wasm format.wasm",
35
38
  "build-js": "tsc && cp format.wasm wasm_exec.js dist",
36
39
  "format": "prettier --write \"**/*.{js,ts,json}\""
37
40
  },