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 +27 -0
- package/dist/guest-parity-CWuYJPbS.mjs +272 -0
- package/dist/guest-parity.d.mts +16 -0
- package/dist/guest-parity.mjs +2 -0
- package/dist/index.mjs +1 -1
- package/dist/vitest-setup.d.mts +1 -0
- package/dist/vitest-setup.mjs +5 -0
- package/package.json +16 -1
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 };
|
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.
|
|
184
|
+
const CLI_VERSION = "0.0.15";
|
|
185
185
|
//#endregion
|
|
186
186
|
//#region src/prompt.ts
|
|
187
187
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wawesome",
|
|
3
|
-
"version": "0.0.
|
|
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"
|