wawesome 0.0.14 → 0.0.15

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
@@ -215,6 +215,33 @@ Three headers arrive or leave on it, and the stripping is what makes them worth
215
215
  | `x-wawesome-invocation-id` | outbound | The id of this run — the key to fetch its logs with `npx wawesome logs --invocation <id>`. |
216
216
  | `x-wawesome-error` | outbound | Present only when the platform failed, never when your Function did. Its *absence* means the status on the wire is yours — up to the moment your response is committed, and no further. |
217
217
 
218
+ ### Testing against the guest's JavaScript surface
219
+
220
+ Your Function does not run on Node. The engine has no `Intl`, and its `toLocaleString` ignores the
221
+ locale you pass — `(1234.5).toLocaleString('en-US')` comes back as `"1234.5"`, not `"1,234.50"`. On
222
+ Node both work, which is how a green suite ships a Function that throws in production, or renders
223
+ markup the browser then refuses to hydrate.
224
+
225
+ Point your test suite at the guest's surface instead:
226
+
227
+ ```ts
228
+ // vitest.config.ts
229
+ import { defineConfig } from "vitest/config";
230
+
231
+ export default defineConfig({
232
+ test: { setupFiles: ["wawesome/vitest-setup"] },
233
+ });
234
+ ```
235
+
236
+ Templates scaffolded with `wawesome init --template` ship this already. With it in place `Intl` is
237
+ gone, `MessageChannel` is the platform's own implementation rather than Node's, and the
238
+ locale-sensitive methods throw with a message naming the remedy — they throw rather than return the
239
+ engine's unlocalised answer because the platform declares them unsupported, and a wrong string that
240
+ fails nowhere is the thing this is here to stop you shipping.
241
+
242
+ If you bundle an `Intl` polyfill, declare it in your `package.json` as you normally would — a
243
+ dependency that provides `Intl` is left in place rather than stripped out from under you.
244
+
218
245
  ### Local Development / Gateway Overrides
219
246
 
220
247
  If you are running a local gateway or self-hosted instance, you can configure your CLI Gateway URL using any of the
@@ -0,0 +1,272 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ //#region ../../server/guest-surface/message-channel.js
4
+ const ENTANGLED = Symbol("entangled");
5
+ const QUEUE = Symbol("queue");
6
+ const STARTED = Symbol("started");
7
+ const CLOSED = Symbol("closed");
8
+ const LISTENERS = Symbol("listeners");
9
+ const ONMESSAGE = Symbol("onmessage");
10
+ var MessagePort = class {
11
+ constructor() {
12
+ this[ENTANGLED] = null;
13
+ this[QUEUE] = [];
14
+ this[STARTED] = false;
15
+ this[CLOSED] = false;
16
+ this[LISTENERS] = [];
17
+ this[ONMESSAGE] = null;
18
+ this.onmessageerror = null;
19
+ }
20
+ get onmessage() {
21
+ return this[ONMESSAGE];
22
+ }
23
+ set onmessage(handler) {
24
+ this[ONMESSAGE] = handler;
25
+ if (handler) this.start();
26
+ }
27
+ start() {
28
+ if (this[STARTED]) return;
29
+ this[STARTED] = true;
30
+ drain(this);
31
+ }
32
+ close() {
33
+ this[CLOSED] = true;
34
+ const peer = this[ENTANGLED];
35
+ this[ENTANGLED] = null;
36
+ if (peer) peer[ENTANGLED] = null;
37
+ }
38
+ postMessage(data) {
39
+ const peer = this[ENTANGLED];
40
+ if (!peer || peer[CLOSED]) return;
41
+ peer[QUEUE].push(data);
42
+ if (peer[STARTED]) drain(peer);
43
+ }
44
+ addEventListener(type, listener) {
45
+ if (type !== "message" || !listener) return;
46
+ this[LISTENERS].push(listener);
47
+ }
48
+ removeEventListener(type, listener) {
49
+ if (type !== "message") return;
50
+ const at = this[LISTENERS].indexOf(listener);
51
+ if (at !== -1) this[LISTENERS].splice(at, 1);
52
+ }
53
+ dispatchEvent(event) {
54
+ deliver(this, event);
55
+ return true;
56
+ }
57
+ };
58
+ function drain(port) {
59
+ if (port[QUEUE].length === 0) return;
60
+ const data = port[QUEUE].shift();
61
+ setTimeout(() => {
62
+ if (port[CLOSED]) return;
63
+ deliver(port, {
64
+ type: "message",
65
+ data,
66
+ target: port,
67
+ ports: []
68
+ });
69
+ drain(port);
70
+ }, 0);
71
+ }
72
+ function deliver(port, event) {
73
+ if (typeof port.onmessage === "function") port.onmessage(event);
74
+ for (const listener of port[LISTENERS].slice()) if (typeof listener === "function") listener.call(port, event);
75
+ else if (listener && typeof listener.handleEvent === "function") listener.handleEvent(event);
76
+ }
77
+ var MessageChannel = class {
78
+ constructor() {
79
+ this.port1 = new MessagePort();
80
+ this.port2 = new MessagePort();
81
+ this.port1[ENTANGLED] = this.port2;
82
+ this.port2[ENTANGLED] = this.port1;
83
+ }
84
+ };
85
+ //#endregion
86
+ //#region ../../server/guest-surface/locale-methods.js
87
+ function installUnsupportedLocaleMethods(scope, methods, remedy) {
88
+ const replaced = [];
89
+ for (const { target, name } of methods) {
90
+ const owner = resolve(scope, target);
91
+ if (!owner || typeof owner[name] !== "function") continue;
92
+ const message = `wawesome: ${target}.${name} is not supported — ${remedy}.`;
93
+ const thrower = function() {
94
+ throw new TypeError(message);
95
+ };
96
+ Object.defineProperty(thrower, "name", {
97
+ value: name,
98
+ configurable: true
99
+ });
100
+ replaced.push({
101
+ target,
102
+ name,
103
+ owner,
104
+ previous: Object.getOwnPropertyDescriptor(owner, name)
105
+ });
106
+ Object.defineProperty(owner, name, {
107
+ value: thrower,
108
+ writable: true,
109
+ enumerable: false,
110
+ configurable: true
111
+ });
112
+ }
113
+ return replaced;
114
+ }
115
+ function resolve(scope, target) {
116
+ return target.split(".").reduce((current, part) => current == null ? current : current[part], scope);
117
+ }
118
+ //#endregion
119
+ //#region ../../server/guest-surface/surface.json
120
+ var globals = [
121
+ {
122
+ "name": "MessageChannel",
123
+ "status": "shim",
124
+ "shim": "message-channel",
125
+ "why": "The engine has none. react-dom/server.browser constructs one at module scope, so a bundle that reaches for it does not evaluate at all."
126
+ },
127
+ {
128
+ "name": "MessagePort",
129
+ "status": "shim",
130
+ "shim": "message-channel",
131
+ "why": "The other half of the pair: a port handed to code that checks what it received has to be a real constructor."
132
+ },
133
+ {
134
+ "name": "Intl",
135
+ "status": "unsupported",
136
+ "remedy": "bundle an Intl polyfill, for example @formatjs/intl-numberformat",
137
+ "providedBy": [
138
+ "intl",
139
+ "full-icu",
140
+ "@formatjs/intl",
141
+ "@formatjs/intl-*",
142
+ "intl-pluralrules",
143
+ "intl-locales-supported",
144
+ "intl-segmenter-polyfill"
145
+ ]
146
+ }
147
+ ];
148
+ var methods = [
149
+ {
150
+ "target": "Number.prototype",
151
+ "name": "toLocaleString"
152
+ },
153
+ {
154
+ "target": "Date.prototype",
155
+ "name": "toLocaleString"
156
+ },
157
+ {
158
+ "target": "Date.prototype",
159
+ "name": "toLocaleDateString"
160
+ },
161
+ {
162
+ "target": "Date.prototype",
163
+ "name": "toLocaleTimeString"
164
+ },
165
+ {
166
+ "target": "String.prototype",
167
+ "name": "toLocaleLowerCase"
168
+ },
169
+ {
170
+ "target": "String.prototype",
171
+ "name": "toLocaleUpperCase"
172
+ }
173
+ ];
174
+ var methodRemedy$1 = "the engine carries no ICU, so it ignores the locale and returns an unlocalised string; format the value yourself, or bundle a formatting library and call it directly";
175
+ //#endregion
176
+ //#region src/guest-surface.ts
177
+ function declaredGlobals() {
178
+ return globals;
179
+ }
180
+ function declaredMethods() {
181
+ return methods;
182
+ }
183
+ function methodRemedy() {
184
+ return methodRemedy$1;
185
+ }
186
+ function unsupportedGlobals() {
187
+ return declaredGlobals().filter((entry) => entry.status === "unsupported");
188
+ }
189
+ function shimmedGlobals() {
190
+ return declaredGlobals().filter((entry) => entry.status === "shim");
191
+ }
192
+ //#endregion
193
+ //#region src/guest-parity.ts
194
+ const SHIMS = {
195
+ MessageChannel,
196
+ MessagePort
197
+ };
198
+ function applyGuestParity(options = {}) {
199
+ const scope = globalThis;
200
+ const declared = declaredPackages(options.projectDir ?? process.cwd());
201
+ const undo = [];
202
+ const removed = [];
203
+ const exempted = [];
204
+ for (const entry of unsupportedGlobals()) {
205
+ if (isPolyfilled(entry, declared)) {
206
+ exempted.push(entry.name);
207
+ continue;
208
+ }
209
+ const descriptor = Object.getOwnPropertyDescriptor(scope, entry.name);
210
+ if (!descriptor) continue;
211
+ undo.push(() => Object.defineProperty(scope, entry.name, descriptor));
212
+ delete scope[entry.name];
213
+ removed.push(entry.name);
214
+ }
215
+ const shimmed = [];
216
+ for (const entry of shimmedGlobals()) {
217
+ const replacement = SHIMS[entry.name];
218
+ if (!replacement) throw new Error(`wawesome: the guest surface declares ${entry.name} as a platform shim, and no implementation is registered for it.`);
219
+ const descriptor = Object.getOwnPropertyDescriptor(scope, entry.name);
220
+ undo.push(() => {
221
+ if (descriptor) Object.defineProperty(scope, entry.name, descriptor);
222
+ else delete scope[entry.name];
223
+ });
224
+ scope[entry.name] = replacement;
225
+ shimmed.push(entry.name);
226
+ }
227
+ const replaced = installUnsupportedLocaleMethods(scope, declaredMethods(), methodRemedy());
228
+ undo.push(() => {
229
+ for (const { owner, name, previous } of replaced) if (previous) Object.defineProperty(owner, name, previous);
230
+ });
231
+ return {
232
+ removed,
233
+ shimmed,
234
+ replacedMethods: replaced.map(({ target, name }) => `${target}.${name}`),
235
+ exempted,
236
+ restore() {
237
+ for (const step of undo.reverse()) step();
238
+ undo.length = 0;
239
+ }
240
+ };
241
+ }
242
+ /**
243
+ * Read from the manifest the polyfill is already declared in, so parity has no
244
+ * switch of its own that can drift from what the project actually bundles.
245
+ */
246
+ function isPolyfilled(entry, declared) {
247
+ return (entry.providedBy ?? []).some((pattern) => declared.some((name) => matches(pattern, name)));
248
+ }
249
+ function matches(pattern, name) {
250
+ if (!pattern.endsWith("*")) return pattern === name;
251
+ return name.startsWith(pattern.slice(0, -1));
252
+ }
253
+ function declaredPackages(projectDir) {
254
+ const manifest = path.join(projectDir, "package.json");
255
+ if (!fs.existsSync(manifest)) return [];
256
+ let parsed;
257
+ try {
258
+ parsed = JSON.parse(fs.readFileSync(manifest, "utf-8"));
259
+ } catch {
260
+ return [];
261
+ }
262
+ return [
263
+ "dependencies",
264
+ "devDependencies",
265
+ "optionalDependencies"
266
+ ].flatMap((field) => {
267
+ const deps = parsed[field];
268
+ return typeof deps === "object" && deps !== null ? Object.keys(deps) : [];
269
+ });
270
+ }
271
+ //#endregion
272
+ export { applyGuestParity as t };
@@ -0,0 +1,16 @@
1
+ //#region src/guest-parity.d.ts
2
+ interface GuestParityOptions {
3
+ /** Where the polyfills are declared. Defaults to the working directory. */
4
+ projectDir?: string;
5
+ }
6
+ interface GuestParity {
7
+ removed: string[];
8
+ shimmed: string[];
9
+ replacedMethods: string[];
10
+ /** Left in place because the project bundles a polyfill for them. */
11
+ exempted: string[];
12
+ restore(): void;
13
+ }
14
+ declare function applyGuestParity(options?: GuestParityOptions): GuestParity;
15
+ //#endregion
16
+ export { GuestParity, GuestParityOptions, applyGuestParity };
@@ -0,0 +1,2 @@
1
+ import { t as applyGuestParity } from "./guest-parity-CWuYJPbS.mjs";
2
+ export { applyGuestParity };
package/dist/index.mjs CHANGED
@@ -181,7 +181,7 @@ async function buildJs(entryInput, options) {
181
181
  * that has to name this version — `--version`, the dependency a scaffolded
182
182
  * project pins — reads it here, so a release bumps one file.
183
183
  */
184
- const CLI_VERSION = "0.0.14";
184
+ const CLI_VERSION = "0.0.15";
185
185
  //#endregion
186
186
  //#region src/prompt.ts
187
187
  /**
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,5 @@
1
+ import { t as applyGuestParity } from "./guest-parity-CWuYJPbS.mjs";
2
+ //#region src/vitest-setup.ts
3
+ applyGuestParity();
4
+ //#endregion
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,21 @@
8
8
  },
9
9
  "main": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.mts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.mts",
14
+ "default": "./dist/index.mjs"
15
+ },
16
+ "./guest-parity": {
17
+ "types": "./dist/guest-parity.d.mts",
18
+ "default": "./dist/guest-parity.mjs"
19
+ },
20
+ "./vitest-setup": {
21
+ "types": "./dist/vitest-setup.d.mts",
22
+ "default": "./dist/vitest-setup.mjs"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
11
26
  "files": [
12
27
  "bin",
13
28
  "dist"