wawesome 0.21.1 → 0.23.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/LICENSE +21 -0
- package/dist/assets-CtoyN8P3.mjs +333 -0
- package/dist/{guest-parity-W61MH7Fb.mjs → guest-parity-DA7x-8BC.mjs} +1 -1
- package/dist/guest-parity.mjs +1 -1
- package/dist/{guest-surface-D5X-IMAN.mjs → guest-surface-BwrNP2Cz.mjs} +9 -1
- package/dist/index.mjs +53 -311
- package/dist/local-modules-CAHr4SPW.mjs +225 -0
- package/dist/local-modules.d.mts +16 -0
- package/dist/local-modules.mjs +2 -0
- package/dist/vite.d.mts +8 -2
- package/dist/vite.mjs +53 -3
- package/dist/vitest-setup.mjs +8 -1
- package/package.json +7 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wawesome.io
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
//#region src/errors.ts
|
|
6
|
+
/**
|
|
7
|
+
* A rejection carrying the gateway's own prose and, where given, its reason.
|
|
8
|
+
*
|
|
9
|
+
* The call's trace id rides in the message itself, on a line of its own, so
|
|
10
|
+
* every place that prints a refusal prints the one thing support can search by.
|
|
11
|
+
*/
|
|
12
|
+
var GatewayError = class extends Error {
|
|
13
|
+
status;
|
|
14
|
+
reason;
|
|
15
|
+
/** The raw response, for `--verbose` to print when the prose is not enough. */
|
|
16
|
+
body;
|
|
17
|
+
traceId;
|
|
18
|
+
constructor(message, status, reason, body = "", traceId) {
|
|
19
|
+
super(traceId ? `${message}\n${traceLine(traceId)}` : message);
|
|
20
|
+
this.name = "GatewayError";
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.reason = reason;
|
|
23
|
+
this.body = body;
|
|
24
|
+
this.traceId = traceId;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function traceLine(traceId) {
|
|
28
|
+
return `Trace ID: ${traceId}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read a failed response into a `GatewayError`.
|
|
32
|
+
*
|
|
33
|
+
* `fallback` is used only when the response carries no prose of its own — a
|
|
34
|
+
* proxy's HTML error page, or an empty body. A 401 is deliberately *not* special
|
|
35
|
+
* cased here: what to say about an expired session depends on what the caller
|
|
36
|
+
* was doing, so callers handle it before reaching this.
|
|
37
|
+
*/
|
|
38
|
+
async function asGatewayError(res, fallback) {
|
|
39
|
+
const traceId = res.headers?.get("x-wawesome-trace-id") ?? void 0;
|
|
40
|
+
return rejectionOf(await res.text().catch(() => ""), res.status, fallback, traceId);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The same reading, for a caller that has already taken the body off the wire.
|
|
44
|
+
* `traceId` is the header's, for a body that never made it here as JSON.
|
|
45
|
+
*/
|
|
46
|
+
function rejectionOf(body, status, fallback, traceId) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(body);
|
|
49
|
+
return new GatewayError(parsed.error || fallback, status, parsed.reason, body, parsed.trace_id || traceId);
|
|
50
|
+
} catch {
|
|
51
|
+
return new GatewayError(fallback, status, void 0, body, traceId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Why the gateway refused, where what was thrown might not be a refusal at all. */
|
|
55
|
+
function reasonOf(err) {
|
|
56
|
+
return err instanceof GatewayError ? err.reason : void 0;
|
|
57
|
+
}
|
|
58
|
+
/** What went wrong, as text, whatever was thrown. */
|
|
59
|
+
function errorText(err) {
|
|
60
|
+
return err instanceof Error ? err.message : String(err);
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/credential.ts
|
|
64
|
+
const CREDENTIAL_ENV_VAR = "WAWESOME_DEPLOY_CREDENTIAL";
|
|
65
|
+
const SECRET_PREFIX = "wawe_";
|
|
66
|
+
function credentialFromEnvironment() {
|
|
67
|
+
const secret = process.env[CREDENTIAL_ENV_VAR]?.trim();
|
|
68
|
+
return secret ? secret : null;
|
|
69
|
+
}
|
|
70
|
+
/** Enough to match a credential against the one in a CI settings page, and no more. */
|
|
71
|
+
function credentialPrefix(secret) {
|
|
72
|
+
return secret.slice(0, 11);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A credential has no session behind it, so the platform's own reason — unknown,
|
|
76
|
+
* revoked, expired, or a scope it does not carry — is the whole answer, and
|
|
77
|
+
* "sign in again" names a door a CI runner cannot open.
|
|
78
|
+
*/
|
|
79
|
+
function unauthorizedMessage(status, body) {
|
|
80
|
+
const secret = credentialFromEnvironment();
|
|
81
|
+
if (!secret) {
|
|
82
|
+
const refusal = rejectionOf(body, status, `The platform refused this request (HTTP ${status}).`);
|
|
83
|
+
if (status !== 401) return refusal.message;
|
|
84
|
+
const expired = "Authentication expired. Run 'wawesome login' again.";
|
|
85
|
+
return refusal.traceId ? `${expired}\n${traceLine(refusal.traceId)}` : expired;
|
|
86
|
+
}
|
|
87
|
+
return rejectionOf(body, status, `The deploy credential ${credentialPrefix(secret)} was refused (HTTP ${status}).`).message;
|
|
88
|
+
}
|
|
89
|
+
function reportSessionCommandRefused(secret, consequence) {
|
|
90
|
+
console.error(`[wawesome] Error: ${CREDENTIAL_ENV_VAR} is set, so this CLI authenticates as deploy credential ${credentialPrefix(secret)}.`);
|
|
91
|
+
console.error(`[wawesome] ${consequence} Unset ${CREDENTIAL_ENV_VAR} first.`);
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/config.ts
|
|
95
|
+
/** Path to the user-level settings file (~/.wawesome/settings.json) */
|
|
96
|
+
function getSettingsPath() {
|
|
97
|
+
return path.join(os.homedir(), ".wawesome", "settings.json");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Read stored user settings from ~/.wawesome/settings.json.
|
|
101
|
+
*/
|
|
102
|
+
function readSettings() {
|
|
103
|
+
const settingsPath = getSettingsPath();
|
|
104
|
+
if (!fs.existsSync(settingsPath)) return {};
|
|
105
|
+
try {
|
|
106
|
+
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
107
|
+
return JSON.parse(raw);
|
|
108
|
+
} catch {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const GATEWAY_ENV_NAMES = ["GATEWAY_URL", "WAWESOME_GATEWAY_URL"];
|
|
113
|
+
const DASHBOARD_ENV_NAMES = ["DASHBOARD_URL", "WAWESOME_DASHBOARD_URL"];
|
|
114
|
+
const GATEWAY_URL_FALLBACK = "https://api.wawesome.io";
|
|
115
|
+
const DASHBOARD_URL_FALLBACK = "https://dashboard.wawesome.io";
|
|
116
|
+
function resolveAddress(overrideUrl, envNames, settingsKey, stored, fallback) {
|
|
117
|
+
if (overrideUrl) return overrideUrl;
|
|
118
|
+
for (const name of envNames) {
|
|
119
|
+
const fromEnv = process.env[name];
|
|
120
|
+
if (fromEnv) return fromEnv;
|
|
121
|
+
}
|
|
122
|
+
const configured = readSettings()[settingsKey];
|
|
123
|
+
if (configured && typeof configured === "string") return configured;
|
|
124
|
+
return stored || fallback;
|
|
125
|
+
}
|
|
126
|
+
function getGatewayUrl(overrideUrl) {
|
|
127
|
+
return resolveAddress(overrideUrl, GATEWAY_ENV_NAMES, "gateway_url", readStoredCredentials()?.gateway_url, GATEWAY_URL_FALLBACK);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A tenant token is only valid at the gateway that issued it, so every other
|
|
131
|
+
* command has to follow the credentials. A login holds no token yet, and
|
|
132
|
+
* reading that rung here made a local gateway outlive the removal of every
|
|
133
|
+
* documented override, since only `logout` clears it. A **Deploy credential**
|
|
134
|
+
* reads the same rungs, and for a sharper reason: following a stale file would
|
|
135
|
+
* present it at a gateway that never minted it.
|
|
136
|
+
*/
|
|
137
|
+
function resolveGatewayUrlWithoutSession(overrideUrl) {
|
|
138
|
+
return resolveAddress(overrideUrl, GATEWAY_ENV_NAMES, "gateway_url", void 0, GATEWAY_URL_FALLBACK);
|
|
139
|
+
}
|
|
140
|
+
getGatewayUrl();
|
|
141
|
+
function getDashboardUrl(overrideUrl) {
|
|
142
|
+
return resolveAddress(overrideUrl, DASHBOARD_ENV_NAMES, "dashboard_url", readStoredCredentials()?.dashboard_url, DASHBOARD_URL_FALLBACK);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The dashboard for the environment this login is going to.
|
|
146
|
+
*
|
|
147
|
+
* Taken from the address the platform handed back to open, so a login that
|
|
148
|
+
* configures nothing stores the dashboard belonging to the gateway it signed
|
|
149
|
+
* into. An explicit flag, `DASHBOARD_URL` or a settings file still outrank it,
|
|
150
|
+
* on the rung they outrank every other address on.
|
|
151
|
+
*/
|
|
152
|
+
function resolveDashboardUrlForLogin(overrideUrl, signInUrl) {
|
|
153
|
+
return resolveAddress(overrideUrl, DASHBOARD_ENV_NAMES, "dashboard_url", originOf(signInUrl), DASHBOARD_URL_FALLBACK);
|
|
154
|
+
}
|
|
155
|
+
/** Undefined rather than throwing: an unreadable address falls to the rung below. */
|
|
156
|
+
function originOf(url) {
|
|
157
|
+
try {
|
|
158
|
+
return new URL(url).origin;
|
|
159
|
+
} catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const LOGIN_CALLBACK_PORT = 9999;
|
|
164
|
+
/** Path to the user-level credentials file */
|
|
165
|
+
function getCredentialsPath() {
|
|
166
|
+
return path.join(os.homedir(), ".wawesome", "credentials.json");
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* What this process authenticates with: the **Deploy credential** in the
|
|
170
|
+
* environment where one is set, and otherwise the session `login` left on disk.
|
|
171
|
+
*
|
|
172
|
+
* The environment wins outright rather than filling a gap, so a CI runner
|
|
173
|
+
* carrying a stale cached home directory still authenticates as itself.
|
|
174
|
+
*/
|
|
175
|
+
function readCredentials() {
|
|
176
|
+
const secret = credentialFromEnvironment();
|
|
177
|
+
return secret ? environmentCredential(secret) : readStoredCredentials();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A credential wears the shape of a session so that every command reaches the
|
|
181
|
+
* platform the same way. What it does not carry is the point: no refresh token,
|
|
182
|
+
* because there is nothing behind it to renew, and no Tenant, because the
|
|
183
|
+
* platform resolves that from the credential.
|
|
184
|
+
*/
|
|
185
|
+
function environmentCredential(secret) {
|
|
186
|
+
return {
|
|
187
|
+
gateway_url: resolveGatewayUrlWithoutSession(void 0),
|
|
188
|
+
tenant_jwt: secret,
|
|
189
|
+
credential_prefix: credentialPrefix(secret)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** The session on disk, whatever the environment says. */
|
|
193
|
+
function readStoredCredentials() {
|
|
194
|
+
const credPath = getCredentialsPath();
|
|
195
|
+
if (!fs.existsSync(credPath)) return null;
|
|
196
|
+
try {
|
|
197
|
+
const raw = fs.readFileSync(credPath, "utf-8");
|
|
198
|
+
return JSON.parse(raw);
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Write credentials to disk, creating the directory if needed.
|
|
205
|
+
*
|
|
206
|
+
* Owner-only, because the file holds a refresh token. The `chmod` is what
|
|
207
|
+
* tightens a file an older CLI left world-readable; a mode passed to
|
|
208
|
+
* `writeFileSync` applies only where it creates the file.
|
|
209
|
+
*/
|
|
210
|
+
function writeCredentials(creds) {
|
|
211
|
+
if (credentialFromEnvironment()) return;
|
|
212
|
+
const credPath = getCredentialsPath();
|
|
213
|
+
const dir = path.dirname(credPath);
|
|
214
|
+
fs.mkdirSync(dir, {
|
|
215
|
+
recursive: true,
|
|
216
|
+
mode: 448
|
|
217
|
+
});
|
|
218
|
+
fs.writeFileSync(credPath, JSON.stringify(creds, null, 2), {
|
|
219
|
+
encoding: "utf-8",
|
|
220
|
+
mode: 384
|
|
221
|
+
});
|
|
222
|
+
fs.chmodSync(credPath, 384);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Delete stored credentials.
|
|
226
|
+
*/
|
|
227
|
+
function deleteCredentials() {
|
|
228
|
+
const credPath = getCredentialsPath();
|
|
229
|
+
if (fs.existsSync(credPath)) {
|
|
230
|
+
fs.rmSync(credPath, { force: true });
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
function getFunctionConfigPath(projectDir) {
|
|
236
|
+
return path.join(projectDir || process.cwd(), "wawesome-function.json");
|
|
237
|
+
}
|
|
238
|
+
function readFunctionConfig(projectDir) {
|
|
239
|
+
const configPath = getFunctionConfigPath(projectDir);
|
|
240
|
+
if (!fs.existsSync(configPath)) return null;
|
|
241
|
+
try {
|
|
242
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
243
|
+
return JSON.parse(raw);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/assets.ts
|
|
250
|
+
/**
|
|
251
|
+
* The files under `dir`, hashed.
|
|
252
|
+
*
|
|
253
|
+
* Hashes are read off disk in a stream rather than by reading each file whole:
|
|
254
|
+
* a client build's largest chunk is not something to hold in memory just to
|
|
255
|
+
* find out the platform already has it.
|
|
256
|
+
*/
|
|
257
|
+
function collectAssets(dir) {
|
|
258
|
+
return listFiles(dir).map((file) => ({
|
|
259
|
+
...file,
|
|
260
|
+
content_hash: hashFile(file.source)
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
/** The files a deploy of `dir` carries, before any is read. */
|
|
264
|
+
function listFiles(dir) {
|
|
265
|
+
if (!fs.existsSync(dir)) return [];
|
|
266
|
+
const files = [];
|
|
267
|
+
const walk = (current, prefix) => {
|
|
268
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
269
|
+
const absolute = path.join(current, entry.name);
|
|
270
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
271
|
+
if (entry.isDirectory()) walk(absolute, relative);
|
|
272
|
+
else if (entry.isFile()) files.push({
|
|
273
|
+
path: relative,
|
|
274
|
+
size_bytes: fs.statSync(absolute).size,
|
|
275
|
+
source: absolute
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
walk(dir, "");
|
|
280
|
+
return files;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* A **Static asset** a browser renders as a page. Read off the extension, as the
|
|
284
|
+
* content type the platform records is — mirrors `content_type_for_path` in
|
|
285
|
+
* `server/crates/core/src/features/assets/content_type.rs`.
|
|
286
|
+
*/
|
|
287
|
+
function isDocument(assetPath) {
|
|
288
|
+
const name = assetPath.split("/").pop() ?? "";
|
|
289
|
+
const dot = name.lastIndexOf(".");
|
|
290
|
+
const extension = dot === -1 ? "" : name.slice(dot + 1).toLowerCase();
|
|
291
|
+
return extension === "html" || extension === "htm";
|
|
292
|
+
}
|
|
293
|
+
function hashFile(file) {
|
|
294
|
+
return sha256(fs.readFileSync(file));
|
|
295
|
+
}
|
|
296
|
+
function sha256(bytes) {
|
|
297
|
+
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* What this deploy *is*: the server bundle together with the sorted set of
|
|
301
|
+
* asset path and content-hash pairs, the same set for its **Private file**s
|
|
302
|
+
* marked apart, and the **Fallback document** it declares.
|
|
303
|
+
*
|
|
304
|
+
* Computed here, before anything is uploaded, so a deploy that changed nothing
|
|
305
|
+
* is refused before a byte moves. Mirrors `deploy_digest` in
|
|
306
|
+
* `server/crates/core/src/features/assets/identity.rs` — the gateway recomputes
|
|
307
|
+
* it from what actually arrives, so the two have to agree exactly.
|
|
308
|
+
*
|
|
309
|
+
* A deploy declaring no fallback and no private files contributes nothing here
|
|
310
|
+
* for either rather than a stated absence, so every digest computed before
|
|
311
|
+
* they existed is unmoved.
|
|
312
|
+
*/
|
|
313
|
+
const DEPLOY_DIGEST_DOMAIN = "wawesome-deploy-v1";
|
|
314
|
+
function deployDigest(bundle, assets, fallback, privateFiles = []) {
|
|
315
|
+
const byPath = (files) => [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
316
|
+
const digest = crypto.createHash("sha256");
|
|
317
|
+
digest.update(`${DEPLOY_DIGEST_DOMAIN}\n`);
|
|
318
|
+
digest.update(`${sha256(bundle)}\n`);
|
|
319
|
+
for (const asset of byPath(assets)) digest.update(`${asset.path}\0${asset.content_hash}\n`);
|
|
320
|
+
for (const file of byPath(privateFiles)) digest.update(`private\0${file.path}\0${file.content_hash}\n`);
|
|
321
|
+
if (fallback) digest.update(`fallback\0${fallback.path}\0${fallback.status}\n`);
|
|
322
|
+
return digest.digest("hex");
|
|
323
|
+
}
|
|
324
|
+
/** The manifest as the gateway reads it — the bytes on disk are the CLI's business. */
|
|
325
|
+
function manifestOf(assets) {
|
|
326
|
+
return assets.map(({ path, content_hash, size_bytes }) => ({
|
|
327
|
+
path,
|
|
328
|
+
content_hash,
|
|
329
|
+
size_bytes
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
export { asGatewayError as C, rejectionOf as E, GatewayError as S, reasonOf as T, SECRET_PREFIX as _, manifestOf as a, reportSessionCommandRefused as b, getDashboardUrl as c, readCredentials as d, readFunctionConfig as f, CREDENTIAL_ENV_VAR as g, writeCredentials as h, listFiles as i, getFunctionConfigPath as l, resolveGatewayUrlWithoutSession as m, deployDigest as n, LOGIN_CALLBACK_PORT as o, resolveDashboardUrlForLogin as p, isDocument as r, deleteCredentials as s, collectAssets as t, getGatewayUrl as u, credentialFromEnvironment as v, errorText as w, unauthorizedMessage as x, credentialPrefix as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as shimmedGlobals, i as polyfilledGlobals, o as unsupportedGlobals, r as methodRemedy, t as declaredMethods } from "./guest-surface-BwrNP2Cz.mjs";
|
|
2
2
|
//#region ../../server/guest-surface/message-channel.js
|
|
3
3
|
const ENTANGLED = Symbol("entangled");
|
|
4
4
|
const QUEUE = Symbol("queue");
|
package/dist/guest-parity.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as applyGuestParity } from "./guest-parity-
|
|
1
|
+
import { t as applyGuestParity } from "./guest-parity-DA7x-8BC.mjs";
|
|
2
2
|
export { applyGuestParity };
|
|
@@ -130,6 +130,11 @@ var globals = [
|
|
|
130
130
|
"remedy": "the guest runs one thread and shares memory with nothing; use an ArrayBuffer"
|
|
131
131
|
}
|
|
132
132
|
];
|
|
133
|
+
var modules = [{
|
|
134
|
+
"specifiers": ["node:fs/promises", "fs/promises"],
|
|
135
|
+
"shim": "fs-promises",
|
|
136
|
+
"why": "Reads the private files the Version carries. The platform writes the module into the guest folder, so a bundler must leave the import alone."
|
|
137
|
+
}];
|
|
133
138
|
var methods = [
|
|
134
139
|
{
|
|
135
140
|
"target": "Number.prototype",
|
|
@@ -159,6 +164,9 @@ var methods = [
|
|
|
159
164
|
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";
|
|
160
165
|
//#endregion
|
|
161
166
|
//#region src/guest-surface.ts
|
|
167
|
+
function declaredModules() {
|
|
168
|
+
return modules;
|
|
169
|
+
}
|
|
162
170
|
function declaredGlobals() {
|
|
163
171
|
return globals;
|
|
164
172
|
}
|
|
@@ -205,4 +213,4 @@ function declaredPackages(projectDir) {
|
|
|
205
213
|
});
|
|
206
214
|
}
|
|
207
215
|
//#endregion
|
|
208
|
-
export {
|
|
216
|
+
export { shimmedGlobals as a, polyfilledGlobals as i, declaredModules as n, unsupportedGlobals as o, methodRemedy as r, declaredMethods as t };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { C as asGatewayError, E as rejectionOf, S as GatewayError, T as reasonOf, a as manifestOf, b as reportSessionCommandRefused, c as getDashboardUrl, d as readCredentials, f as readFunctionConfig, g as CREDENTIAL_ENV_VAR, h as writeCredentials, l as getFunctionConfigPath, m as resolveGatewayUrlWithoutSession, n as deployDigest, o as LOGIN_CALLBACK_PORT, p as resolveDashboardUrlForLogin, r as isDocument, s as deleteCredentials, t as collectAssets, u as getGatewayUrl, v as credentialFromEnvironment, w as errorText, x as unauthorizedMessage, y as credentialPrefix } from "./assets-CtoyN8P3.mjs";
|
|
2
|
+
import { i as polyfilledGlobals, n as declaredModules, o as unsupportedGlobals, r as methodRemedy, t as declaredMethods } from "./guest-surface-BwrNP2Cz.mjs";
|
|
2
3
|
import cac from "cac";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
@@ -26,230 +27,6 @@ function lockup(stream = process.stdout, env = process.env) {
|
|
|
26
27
|
}
|
|
27
28
|
const LOCKUP_HTML = "<div style=\"margin-bottom: 24px; font: 700 20px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: -0.02em; color: #e8eaf0\"><span style=\"font-size: 22px; letter-spacing: -0.06em\"><span style=\"color: #6b7cff\">[</span>w<span style=\"color: #6b7cff\">]</span></span> wawesome</div>";
|
|
28
29
|
//#endregion
|
|
29
|
-
//#region src/errors.ts
|
|
30
|
-
/** A rejection carrying the gateway's own prose and, where given, its reason. */
|
|
31
|
-
var GatewayError = class extends Error {
|
|
32
|
-
status;
|
|
33
|
-
reason;
|
|
34
|
-
/** The raw response, for `--verbose` to print when the prose is not enough. */
|
|
35
|
-
body;
|
|
36
|
-
constructor(message, status, reason, body = "") {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = "GatewayError";
|
|
39
|
-
this.status = status;
|
|
40
|
-
this.reason = reason;
|
|
41
|
-
this.body = body;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* Read a failed response into a `GatewayError`.
|
|
46
|
-
*
|
|
47
|
-
* `fallback` is used only when the response carries no prose of its own — a
|
|
48
|
-
* proxy's HTML error page, or an empty body. A 401 is deliberately *not* special
|
|
49
|
-
* cased here: what to say about an expired session depends on what the caller
|
|
50
|
-
* was doing, so callers handle it before reaching this.
|
|
51
|
-
*/
|
|
52
|
-
async function asGatewayError(res, fallback) {
|
|
53
|
-
return rejectionOf(await res.text().catch(() => ""), res.status, fallback);
|
|
54
|
-
}
|
|
55
|
-
/** The same reading, for a caller that has already taken the body off the wire. */
|
|
56
|
-
function rejectionOf(body, status, fallback) {
|
|
57
|
-
try {
|
|
58
|
-
const parsed = JSON.parse(body);
|
|
59
|
-
return new GatewayError(parsed.error || fallback, status, parsed.reason, body);
|
|
60
|
-
} catch {
|
|
61
|
-
return new GatewayError(fallback, status, void 0, body);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/** Why the gateway refused, where what was thrown might not be a refusal at all. */
|
|
65
|
-
function reasonOf(err) {
|
|
66
|
-
return err instanceof GatewayError ? err.reason : void 0;
|
|
67
|
-
}
|
|
68
|
-
/** What went wrong, as text, whatever was thrown. */
|
|
69
|
-
function errorText(err) {
|
|
70
|
-
return err instanceof Error ? err.message : String(err);
|
|
71
|
-
}
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/credential.ts
|
|
74
|
-
const CREDENTIAL_ENV_VAR = "WAWESOME_DEPLOY_CREDENTIAL";
|
|
75
|
-
function credentialFromEnvironment() {
|
|
76
|
-
const secret = process.env[CREDENTIAL_ENV_VAR]?.trim();
|
|
77
|
-
return secret ? secret : null;
|
|
78
|
-
}
|
|
79
|
-
/** Enough to match a credential against the one in a CI settings page, and no more. */
|
|
80
|
-
function credentialPrefix(secret) {
|
|
81
|
-
return secret.slice(0, 11);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* A credential has no session behind it, so the platform's own reason — unknown,
|
|
85
|
-
* revoked, expired, or a scope it does not carry — is the whole answer, and
|
|
86
|
-
* "sign in again" names a door a CI runner cannot open.
|
|
87
|
-
*/
|
|
88
|
-
function unauthorizedMessage(status, body) {
|
|
89
|
-
const secret = credentialFromEnvironment();
|
|
90
|
-
if (!secret) return status === 401 ? "Authentication expired. Run 'wawesome login' again." : rejectionOf(body, status, `The platform refused this request (HTTP ${status}).`).message;
|
|
91
|
-
return rejectionOf(body, status, `The deploy credential ${credentialPrefix(secret)} was refused (HTTP ${status}).`).message;
|
|
92
|
-
}
|
|
93
|
-
function reportSessionCommandRefused(secret, consequence) {
|
|
94
|
-
console.error(`[wawesome] Error: ${CREDENTIAL_ENV_VAR} is set, so this CLI authenticates as deploy credential ${credentialPrefix(secret)}.`);
|
|
95
|
-
console.error(`[wawesome] ${consequence} Unset ${CREDENTIAL_ENV_VAR} first.`);
|
|
96
|
-
}
|
|
97
|
-
//#endregion
|
|
98
|
-
//#region src/config.ts
|
|
99
|
-
/** Path to the user-level settings file (~/.wawesome/settings.json) */
|
|
100
|
-
function getSettingsPath() {
|
|
101
|
-
return path.join(os.homedir(), ".wawesome", "settings.json");
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Read stored user settings from ~/.wawesome/settings.json.
|
|
105
|
-
*/
|
|
106
|
-
function readSettings() {
|
|
107
|
-
const settingsPath = getSettingsPath();
|
|
108
|
-
if (!fs.existsSync(settingsPath)) return {};
|
|
109
|
-
try {
|
|
110
|
-
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
111
|
-
return JSON.parse(raw);
|
|
112
|
-
} catch {
|
|
113
|
-
return {};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const GATEWAY_ENV_NAMES = ["GATEWAY_URL", "WAWESOME_GATEWAY_URL"];
|
|
117
|
-
const DASHBOARD_ENV_NAMES = ["DASHBOARD_URL", "WAWESOME_DASHBOARD_URL"];
|
|
118
|
-
const GATEWAY_URL_FALLBACK = "https://api.wawesome.io";
|
|
119
|
-
const DASHBOARD_URL_FALLBACK = "https://dashboard.wawesome.io";
|
|
120
|
-
function resolveAddress(overrideUrl, envNames, settingsKey, stored, fallback) {
|
|
121
|
-
if (overrideUrl) return overrideUrl;
|
|
122
|
-
for (const name of envNames) {
|
|
123
|
-
const fromEnv = process.env[name];
|
|
124
|
-
if (fromEnv) return fromEnv;
|
|
125
|
-
}
|
|
126
|
-
const configured = readSettings()[settingsKey];
|
|
127
|
-
if (configured && typeof configured === "string") return configured;
|
|
128
|
-
return stored || fallback;
|
|
129
|
-
}
|
|
130
|
-
function getGatewayUrl(overrideUrl) {
|
|
131
|
-
return resolveAddress(overrideUrl, GATEWAY_ENV_NAMES, "gateway_url", readStoredCredentials()?.gateway_url, GATEWAY_URL_FALLBACK);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* A tenant token is only valid at the gateway that issued it, so every other
|
|
135
|
-
* command has to follow the credentials. A login holds no token yet, and
|
|
136
|
-
* reading that rung here made a local gateway outlive the removal of every
|
|
137
|
-
* documented override, since only `logout` clears it. A **Deploy credential**
|
|
138
|
-
* reads the same rungs, and for a sharper reason: following a stale file would
|
|
139
|
-
* present it at a gateway that never minted it.
|
|
140
|
-
*/
|
|
141
|
-
function resolveGatewayUrlWithoutSession(overrideUrl) {
|
|
142
|
-
return resolveAddress(overrideUrl, GATEWAY_ENV_NAMES, "gateway_url", void 0, GATEWAY_URL_FALLBACK);
|
|
143
|
-
}
|
|
144
|
-
getGatewayUrl();
|
|
145
|
-
function getDashboardUrl(overrideUrl) {
|
|
146
|
-
return resolveAddress(overrideUrl, DASHBOARD_ENV_NAMES, "dashboard_url", readStoredCredentials()?.dashboard_url, DASHBOARD_URL_FALLBACK);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* The dashboard for the environment this login is going to.
|
|
150
|
-
*
|
|
151
|
-
* Taken from the address the platform handed back to open, so a login that
|
|
152
|
-
* configures nothing stores the dashboard belonging to the gateway it signed
|
|
153
|
-
* into. An explicit flag, `DASHBOARD_URL` or a settings file still outrank it,
|
|
154
|
-
* on the rung they outrank every other address on.
|
|
155
|
-
*/
|
|
156
|
-
function resolveDashboardUrlForLogin(overrideUrl, signInUrl) {
|
|
157
|
-
return resolveAddress(overrideUrl, DASHBOARD_ENV_NAMES, "dashboard_url", originOf(signInUrl), DASHBOARD_URL_FALLBACK);
|
|
158
|
-
}
|
|
159
|
-
/** Undefined rather than throwing: an unreadable address falls to the rung below. */
|
|
160
|
-
function originOf(url) {
|
|
161
|
-
try {
|
|
162
|
-
return new URL(url).origin;
|
|
163
|
-
} catch {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
const LOGIN_CALLBACK_PORT = 9999;
|
|
168
|
-
/** Path to the user-level credentials file */
|
|
169
|
-
function getCredentialsPath() {
|
|
170
|
-
return path.join(os.homedir(), ".wawesome", "credentials.json");
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* What this process authenticates with: the **Deploy credential** in the
|
|
174
|
-
* environment where one is set, and otherwise the session `login` left on disk.
|
|
175
|
-
*
|
|
176
|
-
* The environment wins outright rather than filling a gap, so a CI runner
|
|
177
|
-
* carrying a stale cached home directory still authenticates as itself.
|
|
178
|
-
*/
|
|
179
|
-
function readCredentials() {
|
|
180
|
-
const secret = credentialFromEnvironment();
|
|
181
|
-
return secret ? environmentCredential(secret) : readStoredCredentials();
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* A credential wears the shape of a session so that every command reaches the
|
|
185
|
-
* platform the same way. What it does not carry is the point: no refresh token,
|
|
186
|
-
* because there is nothing behind it to renew, and no Tenant, because the
|
|
187
|
-
* platform resolves that from the credential.
|
|
188
|
-
*/
|
|
189
|
-
function environmentCredential(secret) {
|
|
190
|
-
return {
|
|
191
|
-
gateway_url: resolveGatewayUrlWithoutSession(void 0),
|
|
192
|
-
tenant_jwt: secret,
|
|
193
|
-
credential_prefix: credentialPrefix(secret)
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
/** The session on disk, whatever the environment says. */
|
|
197
|
-
function readStoredCredentials() {
|
|
198
|
-
const credPath = getCredentialsPath();
|
|
199
|
-
if (!fs.existsSync(credPath)) return null;
|
|
200
|
-
try {
|
|
201
|
-
const raw = fs.readFileSync(credPath, "utf-8");
|
|
202
|
-
return JSON.parse(raw);
|
|
203
|
-
} catch {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Write credentials to disk, creating the directory if needed.
|
|
209
|
-
*
|
|
210
|
-
* Owner-only, because the file holds a refresh token. The `chmod` is what
|
|
211
|
-
* tightens a file an older CLI left world-readable; a mode passed to
|
|
212
|
-
* `writeFileSync` applies only where it creates the file.
|
|
213
|
-
*/
|
|
214
|
-
function writeCredentials(creds) {
|
|
215
|
-
if (credentialFromEnvironment()) return;
|
|
216
|
-
const credPath = getCredentialsPath();
|
|
217
|
-
const dir = path.dirname(credPath);
|
|
218
|
-
fs.mkdirSync(dir, {
|
|
219
|
-
recursive: true,
|
|
220
|
-
mode: 448
|
|
221
|
-
});
|
|
222
|
-
fs.writeFileSync(credPath, JSON.stringify(creds, null, 2), {
|
|
223
|
-
encoding: "utf-8",
|
|
224
|
-
mode: 384
|
|
225
|
-
});
|
|
226
|
-
fs.chmodSync(credPath, 384);
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Delete stored credentials.
|
|
230
|
-
*/
|
|
231
|
-
function deleteCredentials() {
|
|
232
|
-
const credPath = getCredentialsPath();
|
|
233
|
-
if (fs.existsSync(credPath)) {
|
|
234
|
-
fs.rmSync(credPath, { force: true });
|
|
235
|
-
return true;
|
|
236
|
-
}
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
function getFunctionConfigPath(projectDir) {
|
|
240
|
-
return path.join(projectDir || process.cwd(), "wawesome-function.json");
|
|
241
|
-
}
|
|
242
|
-
function readFunctionConfig(projectDir) {
|
|
243
|
-
const configPath = getFunctionConfigPath(projectDir);
|
|
244
|
-
if (!fs.existsSync(configPath)) return null;
|
|
245
|
-
try {
|
|
246
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
247
|
-
return JSON.parse(raw);
|
|
248
|
-
} catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
//#endregion
|
|
253
30
|
//#region src/sourcemap.ts
|
|
254
31
|
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
255
32
|
const NO_POSITION = () => null;
|
|
@@ -861,6 +638,7 @@ async function buildHandler(entryInput, config, options) {
|
|
|
861
638
|
"worker",
|
|
862
639
|
"browser"
|
|
863
640
|
],
|
|
641
|
+
external: declaredModules().flatMap((module) => module.specifiers),
|
|
864
642
|
target: "es2022",
|
|
865
643
|
treeShaking: true,
|
|
866
644
|
minify: true,
|
|
@@ -909,7 +687,7 @@ async function buildHandler(entryInput, config, options) {
|
|
|
909
687
|
* that has to name this version — `--version`, the dependency a scaffolded
|
|
910
688
|
* project pins — reads it here, so a release bumps one file.
|
|
911
689
|
*/
|
|
912
|
-
const CLI_VERSION = "0.
|
|
690
|
+
const CLI_VERSION = "0.23.0";
|
|
913
691
|
//#endregion
|
|
914
692
|
//#region src/cli-login.ts
|
|
915
693
|
const START_PATH = "/v1/cli/login/start";
|
|
@@ -1592,8 +1370,15 @@ function planLimitAdvice(reason) {
|
|
|
1592
1370
|
//#region src/report.ts
|
|
1593
1371
|
/** Where a value sits in the deploy's summary block, so every line under one label agrees. */
|
|
1594
1372
|
const VALUE_COLUMN = " ".repeat(14);
|
|
1595
|
-
/**
|
|
1373
|
+
/**
|
|
1374
|
+
* The platform's own prose, kept inside the block it is printed in. A line
|
|
1375
|
+
* break it already carries stays one, which keeps a refusal's trace id on a
|
|
1376
|
+
* line of its own.
|
|
1377
|
+
*/
|
|
1596
1378
|
function wrapped(said) {
|
|
1379
|
+
return said.split("\n").flatMap(wrappedLine);
|
|
1380
|
+
}
|
|
1381
|
+
function wrappedLine(said) {
|
|
1597
1382
|
const width = 64;
|
|
1598
1383
|
const lines = [];
|
|
1599
1384
|
let line = "";
|
|
@@ -2034,6 +1819,13 @@ function attachedDomainLines(domain, printedBy = "deploy") {
|
|
|
2034
1819
|
`${VALUE_COLUMN}Remove 'domain' from wawesome-function.json, or deploy again`,
|
|
2035
1820
|
`${VALUE_COLUMN}once it is gone to attach it afresh.`
|
|
2036
1821
|
];
|
|
1822
|
+
if (domain.withdrawn) return [
|
|
1823
|
+
"",
|
|
1824
|
+
` Domain: \x1b[33m${hostname}: stopped, because the plan does not include custom domains\x1b[0m`,
|
|
1825
|
+
`${VALUE_COLUMN}It is still attached, and its records and certificate are kept.`,
|
|
1826
|
+
`${VALUE_COLUMN}Move to a paid plan and it answers again within the hour.`,
|
|
1827
|
+
`${VALUE_COLUMN}This app's own address still answers.`
|
|
1828
|
+
];
|
|
2037
1829
|
const legs = domain.legs ?? [];
|
|
2038
1830
|
if (legs.length > 1) return pairLines(domain, legs, printedBy);
|
|
2039
1831
|
const lines = ["", ` Domain: \x1b[36m${hostname}\x1b[0m: ${whereItGotTo(domain)}`];
|
|
@@ -2281,80 +2073,6 @@ function widest(values) {
|
|
|
2281
2073
|
return values.reduce((longest, value) => Math.max(longest, value.length), 0);
|
|
2282
2074
|
}
|
|
2283
2075
|
//#endregion
|
|
2284
|
-
//#region src/assets.ts
|
|
2285
|
-
/**
|
|
2286
|
-
* The files under `dir`, hashed.
|
|
2287
|
-
*
|
|
2288
|
-
* Hashes are read off disk in a stream rather than by reading each file whole:
|
|
2289
|
-
* a client build's largest chunk is not something to hold in memory just to
|
|
2290
|
-
* find out the platform already has it.
|
|
2291
|
-
*/
|
|
2292
|
-
function collectAssets(dir) {
|
|
2293
|
-
if (!fs.existsSync(dir)) return [];
|
|
2294
|
-
const assets = [];
|
|
2295
|
-
const walk = (current, prefix) => {
|
|
2296
|
-
for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2297
|
-
const absolute = path.join(current, entry.name);
|
|
2298
|
-
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2299
|
-
if (entry.isDirectory()) walk(absolute, relative);
|
|
2300
|
-
else if (entry.isFile()) assets.push({
|
|
2301
|
-
path: relative,
|
|
2302
|
-
content_hash: hashFile(absolute),
|
|
2303
|
-
size_bytes: fs.statSync(absolute).size,
|
|
2304
|
-
source: absolute
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
|
-
};
|
|
2308
|
-
walk(dir, "");
|
|
2309
|
-
return assets;
|
|
2310
|
-
}
|
|
2311
|
-
/**
|
|
2312
|
-
* A **Static asset** a browser renders as a page. Read off the extension, as the
|
|
2313
|
-
* content type the platform records is — mirrors `content_type_for_path` in
|
|
2314
|
-
* `server/crates/core/src/features/assets/content_type.rs`.
|
|
2315
|
-
*/
|
|
2316
|
-
function isDocument(assetPath) {
|
|
2317
|
-
const name = assetPath.split("/").pop() ?? "";
|
|
2318
|
-
const dot = name.lastIndexOf(".");
|
|
2319
|
-
const extension = dot === -1 ? "" : name.slice(dot + 1).toLowerCase();
|
|
2320
|
-
return extension === "html" || extension === "htm";
|
|
2321
|
-
}
|
|
2322
|
-
function hashFile(file) {
|
|
2323
|
-
return sha256(fs.readFileSync(file));
|
|
2324
|
-
}
|
|
2325
|
-
function sha256(bytes) {
|
|
2326
|
-
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
2327
|
-
}
|
|
2328
|
-
/**
|
|
2329
|
-
* What this deploy *is*: the server bundle together with the sorted set of
|
|
2330
|
-
* asset path and content-hash pairs, and the **Fallback document** it declares.
|
|
2331
|
-
*
|
|
2332
|
-
* Computed here, before anything is uploaded, so a deploy that changed nothing
|
|
2333
|
-
* is refused before a byte moves. Mirrors `deploy_digest` in
|
|
2334
|
-
* `server/crates/core/src/features/assets/identity.rs` — the gateway recomputes
|
|
2335
|
-
* it from what actually arrives, so the two have to agree exactly.
|
|
2336
|
-
*
|
|
2337
|
-
* A deploy declaring no fallback contributes nothing here rather than a stated
|
|
2338
|
-
* absence, so every digest computed before fallbacks existed is unmoved.
|
|
2339
|
-
*/
|
|
2340
|
-
const DEPLOY_DIGEST_DOMAIN = "wawesome-deploy-v1";
|
|
2341
|
-
function deployDigest(bundle, assets, fallback) {
|
|
2342
|
-
const digest = crypto.createHash("sha256");
|
|
2343
|
-
digest.update(`${DEPLOY_DIGEST_DOMAIN}\n`);
|
|
2344
|
-
digest.update(`${sha256(bundle)}\n`);
|
|
2345
|
-
for (const asset of [...assets].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) digest.update(`${asset.path}\0${asset.content_hash}\n`);
|
|
2346
|
-
if (fallback) digest.update(`fallback\0${fallback.path}\0${fallback.status}\n`);
|
|
2347
|
-
return digest.digest("hex");
|
|
2348
|
-
}
|
|
2349
|
-
/** The manifest as the gateway reads it — the bytes on disk are the CLI's business. */
|
|
2350
|
-
function manifestOf(assets) {
|
|
2351
|
-
return assets.map(({ path, content_hash, size_bytes }) => ({
|
|
2352
|
-
path,
|
|
2353
|
-
content_hash,
|
|
2354
|
-
size_bytes
|
|
2355
|
-
}));
|
|
2356
|
-
}
|
|
2357
|
-
//#endregion
|
|
2358
2076
|
//#region src/prompt.ts
|
|
2359
2077
|
/**
|
|
2360
2078
|
* Open a prompt session on stdin, queueing lines as they arrive.
|
|
@@ -3329,6 +3047,7 @@ async function deploy(entryInput, options) {
|
|
|
3329
3047
|
const declaresSchedules = config.schedules !== void 0;
|
|
3330
3048
|
const declaresFallback = declared.fallback !== void 0;
|
|
3331
3049
|
const assets = config.assets ? collectAssets(path.resolve(config.assets)) : [];
|
|
3050
|
+
const privateFiles = config.files ? collectAssets(path.resolve(config.files)) : [];
|
|
3332
3051
|
const documents = assets.filter((asset) => isDocument(asset.path));
|
|
3333
3052
|
if (jsCode === null && documents.length === 0) {
|
|
3334
3053
|
console.error(`[wawesome] Error: Entry file '${DEFAULT_ENTRY}' not found, and this project carries no pages.`);
|
|
@@ -3337,7 +3056,10 @@ async function deploy(entryInput, options) {
|
|
|
3337
3056
|
console.error("[wawesome] \x1B[36m\"assets\"\x1B[0m at a directory holding an index.html.");
|
|
3338
3057
|
process.exit(1);
|
|
3339
3058
|
}
|
|
3340
|
-
if (assets.length > 0) await uploadAssets(creds, app, funcName, jsCode ?? "",
|
|
3059
|
+
if (assets.length > 0 || privateFiles.length > 0) await uploadAssets(creds, app, funcName, jsCode ?? "", {
|
|
3060
|
+
assets,
|
|
3061
|
+
privateFiles
|
|
3062
|
+
}, declared, isVerbose, declaresSchedules || domainName !== void 0);
|
|
3341
3063
|
console.log(`[wawesome] Uploading code for ${app}/${funcName}...`);
|
|
3342
3064
|
const uploadUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/code`;
|
|
3343
3065
|
if (isVerbose) console.log(`[wawesome:verbose] POST ${uploadUrl}`);
|
|
@@ -3345,7 +3067,7 @@ async function deploy(entryInput, options) {
|
|
|
3345
3067
|
...declaredFields(declared),
|
|
3346
3068
|
...declared.message ? { message: declared.message } : {}
|
|
3347
3069
|
};
|
|
3348
|
-
const uploadRes = assets.length > 0 || declaresSchedules || declaresFallback || Object.keys(declaredOnTheWire).length > 0 || jsCode === null ? await authorizedFetch(uploadUrl, {
|
|
3070
|
+
const uploadRes = assets.length > 0 || privateFiles.length > 0 || declaresSchedules || declaresFallback || domainName !== void 0 || Object.keys(declaredOnTheWire).length > 0 || jsCode === null ? await authorizedFetch(uploadUrl, {
|
|
3349
3071
|
method: "POST",
|
|
3350
3072
|
headers: {
|
|
3351
3073
|
Authorization: `Bearer ${creds.tenant_jwt}`,
|
|
@@ -3354,8 +3076,10 @@ async function deploy(entryInput, options) {
|
|
|
3354
3076
|
body: JSON.stringify({
|
|
3355
3077
|
...jsCode !== null ? { code: jsCode } : {},
|
|
3356
3078
|
...assets.length > 0 ? { assets: manifestOf(assets) } : {},
|
|
3079
|
+
...privateFiles.length > 0 ? { files: manifestOf(privateFiles) } : {},
|
|
3357
3080
|
...declaresSchedules ? { schedules: config.schedules } : {},
|
|
3358
3081
|
...declaresFallback ? { fallback: declared.fallback } : {},
|
|
3082
|
+
...domainName !== void 0 ? { domain: domainName } : {},
|
|
3359
3083
|
...declaredOnTheWire
|
|
3360
3084
|
})
|
|
3361
3085
|
}) : await authorizedFetch(uploadUrl, {
|
|
@@ -3461,6 +3185,7 @@ async function deploy(entryInput, options) {
|
|
|
3461
3185
|
if (version !== void 0) console.log(` Version: ${version}${nothingBuilt ? " (already live, nothing promoted)" : ""}`);
|
|
3462
3186
|
for (const line of handlerLines(bundlePath)) console.log(line);
|
|
3463
3187
|
if (assets.length > 0) console.log(` Assets: ${assets.length}${pageCount(documents.length)}`);
|
|
3188
|
+
if (privateFiles.length > 0) console.log(` Private: ${privateFiles.length}`);
|
|
3464
3189
|
console.log(` Visibility: ${visibility}`);
|
|
3465
3190
|
if (schedules.length > 0) {
|
|
3466
3191
|
console.log("");
|
|
@@ -3510,7 +3235,7 @@ function pageCount(pages) {
|
|
|
3510
3235
|
* The identity of the whole deploy goes with the question, so a redeploy that
|
|
3511
3236
|
* changed nothing at all is refused here — before a byte of it has moved.
|
|
3512
3237
|
*/
|
|
3513
|
-
async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVerbose, continueWhenUnchanged) {
|
|
3238
|
+
async function uploadAssets(creds, app, funcName, bundle, { assets, privateFiles }, declared, isVerbose, continueWhenUnchanged) {
|
|
3514
3239
|
const manifestRes = await authorizedFetch(`${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/assets/manifest`, {
|
|
3515
3240
|
method: "POST",
|
|
3516
3241
|
headers: {
|
|
@@ -3518,8 +3243,9 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
|
|
|
3518
3243
|
"Content-Type": "application/json"
|
|
3519
3244
|
},
|
|
3520
3245
|
body: JSON.stringify({
|
|
3521
|
-
deploy_digest: deployDigest(Buffer.from(bundle, "utf-8"), assets, declared.fallback),
|
|
3246
|
+
deploy_digest: deployDigest(Buffer.from(bundle, "utf-8"), assets, declared.fallback, privateFiles),
|
|
3522
3247
|
assets: manifestOf(assets),
|
|
3248
|
+
...privateFiles.length > 0 ? { files: manifestOf(privateFiles) } : {},
|
|
3523
3249
|
...declaredFields(declared)
|
|
3524
3250
|
})
|
|
3525
3251
|
});
|
|
@@ -3528,7 +3254,7 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
|
|
|
3528
3254
|
if (manifestRes.status === 401 || manifestRes.status === 403) console.error(`[wawesome] Error: ${unauthorizedMessage(manifestRes.status, errorBody)}`);
|
|
3529
3255
|
else if (manifestRes.status === 409) {
|
|
3530
3256
|
if (continueWhenUnchanged) {
|
|
3531
|
-
console.log("[wawesome] Code and files unchanged since the last deploy; applying
|
|
3257
|
+
console.log("[wawesome] Code and files unchanged since the last deploy; applying the config file.");
|
|
3532
3258
|
return;
|
|
3533
3259
|
}
|
|
3534
3260
|
const refusal = rejectionOf(errorBody, manifestRes.status, "This deploy is already deployed.");
|
|
@@ -3546,12 +3272,13 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
|
|
|
3546
3272
|
process.exit(1);
|
|
3547
3273
|
}
|
|
3548
3274
|
const { missing = [] } = await manifestRes.json();
|
|
3549
|
-
const
|
|
3275
|
+
const carried = [...assets, ...privateFiles];
|
|
3276
|
+
const toUpload = [...new Map(carried.filter((file) => missing.includes(file.content_hash)).map((file) => [file.content_hash, file])).values()];
|
|
3550
3277
|
if (toUpload.length === 0) {
|
|
3551
|
-
console.log(`[wawesome] ✅ ${
|
|
3278
|
+
console.log(`[wawesome] ✅ ${carried.length} file(s) already on the platform, nothing to upload.`);
|
|
3552
3279
|
return;
|
|
3553
3280
|
}
|
|
3554
|
-
console.log(`[wawesome] Uploading ${toUpload.length} of ${
|
|
3281
|
+
console.log(`[wawesome] Uploading ${toUpload.length} of ${carried.length} file(s)...`);
|
|
3555
3282
|
for (const asset of toUpload) {
|
|
3556
3283
|
if (isVerbose) console.log(`[wawesome:verbose] PUT ${asset.path} (${asset.size_bytes} bytes)`);
|
|
3557
3284
|
const res = await authorizedFetch(`${creds.gateway_url}/v1/assets/${asset.content_hash}`, () => ({
|
|
@@ -3574,7 +3301,7 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
|
|
|
3574
3301
|
process.exit(1);
|
|
3575
3302
|
}
|
|
3576
3303
|
}
|
|
3577
|
-
console.log(`[wawesome] ✅ ${toUpload.length}
|
|
3304
|
+
console.log(`[wawesome] ✅ ${toUpload.length} file(s) uploaded.`);
|
|
3578
3305
|
}
|
|
3579
3306
|
//#endregion
|
|
3580
3307
|
//#region src/opens-host.ts
|
|
@@ -4438,7 +4165,8 @@ async function promptForDeclaredEnv(session, declared) {
|
|
|
4438
4165
|
console.log(` \x1b[1m${variable.key}\x1b[0m \x1b[90m(${flags.join(", ")})\x1b[0m`);
|
|
4439
4166
|
console.log(` ${variable.description}`);
|
|
4440
4167
|
console.log(` \x1b[90mWhere to find it: ${variable.where_to_find}\x1b[0m`);
|
|
4441
|
-
const
|
|
4168
|
+
const later = variable.required ? " (leave blank to set it after the deploy)" : "";
|
|
4169
|
+
const value = await session.ask(` ${variable.key}?${later}`, "");
|
|
4442
4170
|
console.log("");
|
|
4443
4171
|
answers.push({
|
|
4444
4172
|
declared: variable,
|
|
@@ -5111,6 +4839,20 @@ async function fetchInvocationLogBody(gatewayUrl, tenantJwt, invocationId, isVer
|
|
|
5111
4839
|
}
|
|
5112
4840
|
const body = await res.text();
|
|
5113
4841
|
process.stdout.write(body.endsWith("\n") ? body : body + "\n");
|
|
4842
|
+
const remediation = await fetchRemediation(gatewayUrl, tenantJwt, invocationId);
|
|
4843
|
+
if (remediation?.cause === "egress-blocked") console.log(`[wawesome] An outbound call was blocked (egress-blocked). Open its host under Egress: ${remediation.dashboard_url}`);
|
|
4844
|
+
}
|
|
4845
|
+
async function fetchRemediation(gatewayUrl, tenantJwt, invocationId) {
|
|
4846
|
+
try {
|
|
4847
|
+
const res = await authorizedFetch(`${gatewayUrl}/v1/invocations/${encodeURIComponent(invocationId)}`, {
|
|
4848
|
+
method: "GET",
|
|
4849
|
+
headers: { Authorization: `Bearer ${tenantJwt}` }
|
|
4850
|
+
});
|
|
4851
|
+
if (!res.ok) return void 0;
|
|
4852
|
+
return (await res.json()).remediation;
|
|
4853
|
+
} catch {
|
|
4854
|
+
return;
|
|
4855
|
+
}
|
|
5114
4856
|
}
|
|
5115
4857
|
/**
|
|
5116
4858
|
* List recent invocations for a function.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { f as readFunctionConfig, i as listFiles } from "./assets-CtoyN8P3.mjs";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region ../../server/guest-surface/private-files.js
|
|
5
|
+
const constants = Object.freeze({
|
|
6
|
+
F_OK: 0,
|
|
7
|
+
R_OK: 4,
|
|
8
|
+
W_OK: 2,
|
|
9
|
+
X_OK: 1
|
|
10
|
+
});
|
|
11
|
+
function fsError(code, description, syscall, path) {
|
|
12
|
+
const error = /* @__PURE__ */ new Error(`${code}: ${description}, ${syscall} '${path}'`);
|
|
13
|
+
error.code = code;
|
|
14
|
+
error.syscall = syscall;
|
|
15
|
+
error.path = path;
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
function pathOf(path) {
|
|
19
|
+
if (path instanceof URL) return decodeURIComponent(path.pathname);
|
|
20
|
+
return String(path);
|
|
21
|
+
}
|
|
22
|
+
function keyOf(path) {
|
|
23
|
+
const segments = [];
|
|
24
|
+
for (const segment of path.split("/")) {
|
|
25
|
+
if (segment === "" || segment === ".") continue;
|
|
26
|
+
if (segment === "..") segments.pop();
|
|
27
|
+
else segments.push(segment);
|
|
28
|
+
}
|
|
29
|
+
return segments.join("/");
|
|
30
|
+
}
|
|
31
|
+
var Kind = class {
|
|
32
|
+
#folder;
|
|
33
|
+
constructor(folder) {
|
|
34
|
+
this.#folder = folder;
|
|
35
|
+
}
|
|
36
|
+
isFile() {
|
|
37
|
+
return !this.#folder;
|
|
38
|
+
}
|
|
39
|
+
isDirectory() {
|
|
40
|
+
return this.#folder;
|
|
41
|
+
}
|
|
42
|
+
isSymbolicLink() {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
isBlockDevice() {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
isCharacterDevice() {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
isFIFO() {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
isSocket() {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var Stats = class extends Kind {
|
|
59
|
+
constructor(entry) {
|
|
60
|
+
super(entry.folder);
|
|
61
|
+
this.size = entry.size;
|
|
62
|
+
this.mode = entry.folder ? 16749 : 33060;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var Dirent = class extends Kind {
|
|
66
|
+
constructor(name, parentPath, folder) {
|
|
67
|
+
super(folder);
|
|
68
|
+
this.name = name;
|
|
69
|
+
this.parentPath = parentPath;
|
|
70
|
+
this.path = parentPath;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function readOnly(syscall) {
|
|
74
|
+
return async (path) => {
|
|
75
|
+
throw fsError("EROFS", "read-only file system", syscall, pathOf(path));
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `listing` is `[path, size]` pairs. `fetchFile(path)` answers a Response:
|
|
80
|
+
* 200 with the bytes, 404 for a file that is gone, 413 with the reason for a
|
|
81
|
+
* read past the invocation's cap.
|
|
82
|
+
*/
|
|
83
|
+
function privateFiles(listing, fetchFile) {
|
|
84
|
+
const sizes = new Map(listing);
|
|
85
|
+
const folders = /* @__PURE__ */ new Map([["", /* @__PURE__ */ new Set()]]);
|
|
86
|
+
for (const path of sizes.keys()) {
|
|
87
|
+
const segments = path.split("/");
|
|
88
|
+
for (let depth = 0; depth < segments.length; depth++) {
|
|
89
|
+
const parent = segments.slice(0, depth).join("/");
|
|
90
|
+
if (!folders.has(parent)) folders.set(parent, /* @__PURE__ */ new Set());
|
|
91
|
+
folders.get(parent).add(segments[depth]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function entryAt(name, syscall) {
|
|
95
|
+
const key = keyOf(name);
|
|
96
|
+
if (sizes.has(key)) return {
|
|
97
|
+
key,
|
|
98
|
+
folder: false,
|
|
99
|
+
size: sizes.get(key)
|
|
100
|
+
};
|
|
101
|
+
if (folders.has(key)) return {
|
|
102
|
+
key,
|
|
103
|
+
folder: true,
|
|
104
|
+
size: 0
|
|
105
|
+
};
|
|
106
|
+
throw fsError("ENOENT", "no such file or directory", syscall, name);
|
|
107
|
+
}
|
|
108
|
+
async function readFile(path, options) {
|
|
109
|
+
const name = pathOf(path);
|
|
110
|
+
const encoding = typeof options === "string" ? options : options?.encoding;
|
|
111
|
+
const entry = entryAt(name, "open");
|
|
112
|
+
if (entry.folder) throw fsError("EISDIR", "illegal operation on a directory", "read", name);
|
|
113
|
+
const response = await fetchFile(entry.key);
|
|
114
|
+
if (response.status === 404) throw fsError("ENOENT", "no such file or directory", "open", name);
|
|
115
|
+
if (response.status === 413) throw fsError("EFBIG", await response.text(), "read", name);
|
|
116
|
+
if (!response.ok) throw fsError("EIO", "i/o error", "read", name);
|
|
117
|
+
if (encoding == null) return new Uint8Array(await response.arrayBuffer());
|
|
118
|
+
if (/^utf-?8$/i.test(encoding)) return response.text();
|
|
119
|
+
return new TextDecoder(encoding).decode(await response.arrayBuffer());
|
|
120
|
+
}
|
|
121
|
+
async function readdir(path, options) {
|
|
122
|
+
const name = pathOf(path);
|
|
123
|
+
const entry = entryAt(name, "scandir");
|
|
124
|
+
if (!entry.folder) throw fsError("ENOTDIR", "not a directory", "scandir", name);
|
|
125
|
+
const listed = [];
|
|
126
|
+
const walk = (key, relative, parentPath) => {
|
|
127
|
+
for (const child of [...folders.get(key)].sort()) {
|
|
128
|
+
const childKey = key === "" ? child : `${key}/${child}`;
|
|
129
|
+
const folder = folders.has(childKey);
|
|
130
|
+
const childRelative = relative === "" ? child : `${relative}/${child}`;
|
|
131
|
+
listed.push(options?.withFileTypes ? new Dirent(child, parentPath, folder) : childRelative);
|
|
132
|
+
if (options?.recursive && folder) walk(childKey, childRelative, `${parentPath}/${child}`);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
walk(entry.key, "", name);
|
|
136
|
+
return listed;
|
|
137
|
+
}
|
|
138
|
+
async function stat(path) {
|
|
139
|
+
return new Stats(entryAt(pathOf(path), "stat"));
|
|
140
|
+
}
|
|
141
|
+
async function lstat(path) {
|
|
142
|
+
return new Stats(entryAt(pathOf(path), "lstat"));
|
|
143
|
+
}
|
|
144
|
+
async function access(path, mode = constants.F_OK) {
|
|
145
|
+
const name = pathOf(path);
|
|
146
|
+
const entry = entryAt(name, "access");
|
|
147
|
+
if (mode & constants.W_OK) throw fsError("EROFS", "read-only file system", "access", name);
|
|
148
|
+
if (mode & constants.X_OK && !entry.folder) throw fsError("EACCES", "permission denied", "access", name);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
constants,
|
|
152
|
+
readFile,
|
|
153
|
+
readdir,
|
|
154
|
+
stat,
|
|
155
|
+
lstat,
|
|
156
|
+
access,
|
|
157
|
+
appendFile: readOnly("open"),
|
|
158
|
+
chmod: readOnly("chmod"),
|
|
159
|
+
chown: readOnly("chown"),
|
|
160
|
+
copyFile: readOnly("copyfile"),
|
|
161
|
+
cp: readOnly("cp"),
|
|
162
|
+
lchown: readOnly("lchown"),
|
|
163
|
+
link: readOnly("link"),
|
|
164
|
+
lutimes: readOnly("lutime"),
|
|
165
|
+
mkdir: readOnly("mkdir"),
|
|
166
|
+
mkdtemp: readOnly("mkdtemp"),
|
|
167
|
+
rename: readOnly("rename"),
|
|
168
|
+
rm: readOnly("rm"),
|
|
169
|
+
rmdir: readOnly("rmdir"),
|
|
170
|
+
symlink: readOnly("symlink"),
|
|
171
|
+
truncate: readOnly("open"),
|
|
172
|
+
unlink: readOnly("unlink"),
|
|
173
|
+
utimes: readOnly("utime"),
|
|
174
|
+
writeFile: readOnly("open")
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/local-modules.ts
|
|
179
|
+
/**
|
|
180
|
+
* `DEFAULT_MAX_PRIVATE_FILE_READ_BYTES` in the runner. Production counts it over
|
|
181
|
+
* every read of one invocation; a local run has no invocation, so it holds for
|
|
182
|
+
* each read alone.
|
|
183
|
+
*/
|
|
184
|
+
const MAX_PRIVATE_FILE_READ_BYTES = 8388608;
|
|
185
|
+
const LOCAL_MODULES = { "fs-promises": localFsPromises };
|
|
186
|
+
/** A declared module as the guest gets it, backed by the project on disk. */
|
|
187
|
+
function localModule(shim, projectDir) {
|
|
188
|
+
const create = LOCAL_MODULES[shim];
|
|
189
|
+
if (!create) throw new Error(`wawesome: the guest surface declares the module '${shim}', and no local implementation is registered for it.`);
|
|
190
|
+
const exports = create(projectDir);
|
|
191
|
+
return {
|
|
192
|
+
...exports,
|
|
193
|
+
default: exports
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** Where `wawesome deploy` would take the private files from, if anywhere. */
|
|
197
|
+
function privateFilesDir(projectDir) {
|
|
198
|
+
const files = readFunctionConfig(projectDir)?.files;
|
|
199
|
+
return files ? path.resolve(projectDir, files) : null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The platform's own rules over a listing read fresh on every call, so a file
|
|
203
|
+
* edited while the dev server runs is what the next request reads.
|
|
204
|
+
*/
|
|
205
|
+
function localFsPromises(projectDir) {
|
|
206
|
+
const dir = privateFilesDir(projectDir);
|
|
207
|
+
const current = () => onDisk(dir);
|
|
208
|
+
return Object.fromEntries(Object.entries(onDisk(null)).map(([name, value]) => [name, typeof value === "function" ? (...args) => current()[name](...args) : value]));
|
|
209
|
+
}
|
|
210
|
+
function onDisk(dir) {
|
|
211
|
+
const files = dir ? listFiles(dir) : [];
|
|
212
|
+
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
213
|
+
return privateFiles(files.map((file) => [file.path, file.size_bytes]), async (key) => {
|
|
214
|
+
const file = byPath.get(key);
|
|
215
|
+
if (!file) return new Response(null, { status: 404 });
|
|
216
|
+
if (file.size_bytes > 8388608) return new Response(`one invocation may read at most ${MAX_PRIVATE_FILE_READ_BYTES} bytes of private files`, { status: 413 });
|
|
217
|
+
try {
|
|
218
|
+
return new Response(await fs.promises.readFile(file.source));
|
|
219
|
+
} catch {
|
|
220
|
+
return new Response(null, { status: 404 });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
export { localModule as n, privateFilesDir as r, MAX_PRIVATE_FILE_READ_BYTES as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/local-modules.d.ts
|
|
2
|
+
type LocalModule = Record<string, unknown> & {
|
|
3
|
+
default: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* `DEFAULT_MAX_PRIVATE_FILE_READ_BYTES` in the runner. Production counts it over
|
|
7
|
+
* every read of one invocation; a local run has no invocation, so it holds for
|
|
8
|
+
* each read alone.
|
|
9
|
+
*/
|
|
10
|
+
declare const MAX_PRIVATE_FILE_READ_BYTES: number;
|
|
11
|
+
/** A declared module as the guest gets it, backed by the project on disk. */
|
|
12
|
+
declare function localModule(shim: string, projectDir: string): LocalModule;
|
|
13
|
+
/** Where `wawesome deploy` would take the private files from, if anywhere. */
|
|
14
|
+
declare function privateFilesDir(projectDir: string): string | null;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { LocalModule, MAX_PRIVATE_FILE_READ_BYTES, localModule, privateFilesDir };
|
package/dist/vite.d.mts
CHANGED
|
@@ -18,6 +18,12 @@ interface WawesomeReactOptions {
|
|
|
18
18
|
* route and answer with an empty shell that fails to hydrate. Every URL it
|
|
19
19
|
* carries is a **Request-time base** away from the mount.
|
|
20
20
|
*/
|
|
21
|
-
declare function wawesomeReact(options?: WawesomeReactOptions): Plugin;
|
|
21
|
+
declare function wawesomeReact(options?: WawesomeReactOptions): Plugin[];
|
|
22
|
+
/**
|
|
23
|
+
* Hands the server code the dev server runs the platform's `node:fs/promises`,
|
|
24
|
+
* reading the project's `files` folder. Only the build leaves the import for
|
|
25
|
+
* the platform, so this is a no-op there.
|
|
26
|
+
*/
|
|
27
|
+
declare function wawesomePrivateFiles(): Plugin;
|
|
22
28
|
//#endregion
|
|
23
|
-
export { WawesomeReactOptions, wawesomeReact as default, wawesomeReact };
|
|
29
|
+
export { WawesomeReactOptions, wawesomeReact as default, wawesomeReact, wawesomePrivateFiles };
|
package/dist/vite.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as polyfilledGlobals, n as declaredModules, o as unsupportedGlobals, r as methodRemedy, t as declaredMethods } from "./guest-surface-BwrNP2Cz.mjs";
|
|
2
|
+
import { n as localModule, r as privateFilesDir, t as MAX_PRIVATE_FILE_READ_BYTES } from "./local-modules-CAHr4SPW.mjs";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
4
6
|
//#region src/vite.ts
|
|
7
|
+
const LOCAL_MODULE_PREFIX = "\0wawesome:module:";
|
|
8
|
+
const LOCAL_MODULES_FILE = fileURLToPath(new URL(`./local-modules${path.extname(fileURLToPath(import.meta.url))}`, import.meta.url));
|
|
5
9
|
const DOCUMENT_MODULE_ID = "virtual:wawesome/document";
|
|
6
10
|
const BASE_MODULE_ID = "virtual:wawesome/base";
|
|
7
11
|
const RESOLVED_DOCUMENT = `\0${DOCUMENT_MODULE_ID}`;
|
|
@@ -32,7 +36,7 @@ function wawesomeReact(options = {}) {
|
|
|
32
36
|
let root = process.cwd();
|
|
33
37
|
let dev = null;
|
|
34
38
|
let clientBuild = null;
|
|
35
|
-
return {
|
|
39
|
+
return [wawesomePrivateFiles(), {
|
|
36
40
|
name: "wawesome:react-ssr",
|
|
37
41
|
config(_userConfig, env) {
|
|
38
42
|
const building = env.command === "build";
|
|
@@ -124,6 +128,52 @@ function wawesomeReact(options = {}) {
|
|
|
124
128
|
}
|
|
125
129
|
return documentModule(html, clientEntry, clientBuild);
|
|
126
130
|
}
|
|
131
|
+
}];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Hands the server code the dev server runs the platform's `node:fs/promises`,
|
|
135
|
+
* reading the project's `files` folder. Only the build leaves the import for
|
|
136
|
+
* the platform, so this is a no-op there.
|
|
137
|
+
*/
|
|
138
|
+
function wawesomePrivateFiles() {
|
|
139
|
+
const shims = new Map(declaredModules().flatMap((module) => module.specifiers.map((specifier) => [specifier, module.shim])));
|
|
140
|
+
let root = process.cwd();
|
|
141
|
+
return {
|
|
142
|
+
name: "wawesome:private-files",
|
|
143
|
+
apply: "serve",
|
|
144
|
+
enforce: "pre",
|
|
145
|
+
config() {
|
|
146
|
+
return { resolve: { alias: [...shims.keys()].map((specifier) => ({
|
|
147
|
+
find: new RegExp(`^${specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
|
|
148
|
+
replacement: specifier
|
|
149
|
+
})) } };
|
|
150
|
+
},
|
|
151
|
+
configResolved(config) {
|
|
152
|
+
root = config.root;
|
|
153
|
+
},
|
|
154
|
+
configureServer(server) {
|
|
155
|
+
const dir = privateFilesDir(root);
|
|
156
|
+
if (!dir) return;
|
|
157
|
+
server.config.logger.info(` wawesome node:fs/promises reads ${path.relative(root, dir) || "."} here, as on the guest`);
|
|
158
|
+
server.config.logger.warn(` each read here may take ${MAX_PRIVATE_FILE_READ_BYTES / 1024 / 1024} MiB; the guest counts that over a whole invocation`);
|
|
159
|
+
},
|
|
160
|
+
resolveId(id) {
|
|
161
|
+
if (this.environment.config.consumer !== "server") return;
|
|
162
|
+
const shim = shims.get(id);
|
|
163
|
+
if (shim) return LOCAL_MODULE_PREFIX + shim;
|
|
164
|
+
},
|
|
165
|
+
load(id) {
|
|
166
|
+
if (!id.startsWith(LOCAL_MODULE_PREFIX)) return;
|
|
167
|
+
const shim = id.slice(17);
|
|
168
|
+
const names = Object.keys(localModule(shim, root)).filter((name) => name !== "default");
|
|
169
|
+
return [
|
|
170
|
+
`import { localModule } from ${JSON.stringify(LOCAL_MODULES_FILE)};`,
|
|
171
|
+
`const module = localModule(${JSON.stringify(shim)}, ${JSON.stringify(root)});`,
|
|
172
|
+
`export const { ${names.join(", ")} } = module;`,
|
|
173
|
+
"export default module.default;",
|
|
174
|
+
""
|
|
175
|
+
].join("\n");
|
|
176
|
+
}
|
|
127
177
|
};
|
|
128
178
|
}
|
|
129
179
|
/**
|
|
@@ -244,4 +294,4 @@ function normalize(file) {
|
|
|
244
294
|
return file.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
245
295
|
}
|
|
246
296
|
//#endregion
|
|
247
|
-
export { wawesomeReact as default, wawesomeReact };
|
|
297
|
+
export { wawesomeReact as default, wawesomeReact, wawesomePrivateFiles };
|
package/dist/vitest-setup.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as declaredModules } from "./guest-surface-BwrNP2Cz.mjs";
|
|
2
|
+
import { t as applyGuestParity } from "./guest-parity-DA7x-8BC.mjs";
|
|
3
|
+
import { n as localModule } from "./local-modules-CAHr4SPW.mjs";
|
|
4
|
+
import { vi } from "vitest";
|
|
2
5
|
//#region src/vitest-setup.ts
|
|
3
6
|
applyGuestParity();
|
|
7
|
+
for (const declared of declaredModules()) {
|
|
8
|
+
const module = localModule(declared.shim, process.cwd());
|
|
9
|
+
for (const specifier of declared.specifiers) vi.doMock(specifier, () => module);
|
|
10
|
+
}
|
|
4
11
|
//#endregion
|
|
5
12
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wawesome",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
8
|
"wawesome": "bin/wawesome.js"
|
|
@@ -68,11 +69,15 @@
|
|
|
68
69
|
"vitest": "^4.1.10"
|
|
69
70
|
},
|
|
70
71
|
"peerDependencies": {
|
|
71
|
-
"vite": "^8.0.0"
|
|
72
|
+
"vite": "^8.0.0",
|
|
73
|
+
"vitest": "^4.0.0"
|
|
72
74
|
},
|
|
73
75
|
"peerDependenciesMeta": {
|
|
74
76
|
"vite": {
|
|
75
77
|
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"vitest": {
|
|
80
|
+
"optional": true
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
}
|