wawesome 0.21.1 → 0.22.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 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.
@@ -1,4 +1,4 @@
1
- import { a as unsupportedGlobals, i as shimmedGlobals, n as methodRemedy, r as polyfilledGlobals, t as declaredMethods } from "./guest-surface-D5X-IMAN.mjs";
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");
@@ -1,2 +1,2 @@
1
- import { t as applyGuestParity } from "./guest-parity-W61MH7Fb.mjs";
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 { unsupportedGlobals as a, shimmedGlobals as i, methodRemedy as n, polyfilledGlobals as r, declaredMethods as t };
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,4 @@
1
- import { a as unsupportedGlobals, n as methodRemedy, r as polyfilledGlobals, t as declaredMethods } from "./guest-surface-D5X-IMAN.mjs";
1
+ import { i as polyfilledGlobals, n as declaredModules, o as unsupportedGlobals, r as methodRemedy, t as declaredMethods } from "./guest-surface-BwrNP2Cz.mjs";
2
2
  import cac from "cac";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
@@ -27,20 +27,30 @@ function lockup(stream = process.stdout, env = process.env) {
27
27
  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>&nbsp;wawesome</div>";
28
28
  //#endregion
29
29
  //#region src/errors.ts
30
- /** A rejection carrying the gateway's own prose and, where given, its reason. */
30
+ /**
31
+ * A rejection carrying the gateway's own prose and, where given, its reason.
32
+ *
33
+ * The call's trace id rides in the message itself, on a line of its own, so
34
+ * every place that prints a refusal prints the one thing support can search by.
35
+ */
31
36
  var GatewayError = class extends Error {
32
37
  status;
33
38
  reason;
34
39
  /** The raw response, for `--verbose` to print when the prose is not enough. */
35
40
  body;
36
- constructor(message, status, reason, body = "") {
37
- super(message);
41
+ traceId;
42
+ constructor(message, status, reason, body = "", traceId) {
43
+ super(traceId ? `${message}\n${traceLine(traceId)}` : message);
38
44
  this.name = "GatewayError";
39
45
  this.status = status;
40
46
  this.reason = reason;
41
47
  this.body = body;
48
+ this.traceId = traceId;
42
49
  }
43
50
  };
51
+ function traceLine(traceId) {
52
+ return `Trace ID: ${traceId}`;
53
+ }
44
54
  /**
45
55
  * Read a failed response into a `GatewayError`.
46
56
  *
@@ -50,15 +60,19 @@ var GatewayError = class extends Error {
50
60
  * was doing, so callers handle it before reaching this.
51
61
  */
52
62
  async function asGatewayError(res, fallback) {
53
- return rejectionOf(await res.text().catch(() => ""), res.status, fallback);
63
+ const traceId = res.headers?.get("x-wawesome-trace-id") ?? void 0;
64
+ return rejectionOf(await res.text().catch(() => ""), res.status, fallback, traceId);
54
65
  }
55
- /** The same reading, for a caller that has already taken the body off the wire. */
56
- function rejectionOf(body, status, fallback) {
66
+ /**
67
+ * The same reading, for a caller that has already taken the body off the wire.
68
+ * `traceId` is the header's, for a body that never made it here as JSON.
69
+ */
70
+ function rejectionOf(body, status, fallback, traceId) {
57
71
  try {
58
72
  const parsed = JSON.parse(body);
59
- return new GatewayError(parsed.error || fallback, status, parsed.reason, body);
73
+ return new GatewayError(parsed.error || fallback, status, parsed.reason, body, parsed.trace_id || traceId);
60
74
  } catch {
61
- return new GatewayError(fallback, status, void 0, body);
75
+ return new GatewayError(fallback, status, void 0, body, traceId);
62
76
  }
63
77
  }
64
78
  /** Why the gateway refused, where what was thrown might not be a refusal at all. */
@@ -87,7 +101,12 @@ function credentialPrefix(secret) {
87
101
  */
88
102
  function unauthorizedMessage(status, body) {
89
103
  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;
104
+ if (!secret) {
105
+ const refusal = rejectionOf(body, status, `The platform refused this request (HTTP ${status}).`);
106
+ if (status !== 401) return refusal.message;
107
+ const expired = "Authentication expired. Run 'wawesome login' again.";
108
+ return refusal.traceId ? `${expired}\n${traceLine(refusal.traceId)}` : expired;
109
+ }
91
110
  return rejectionOf(body, status, `The deploy credential ${credentialPrefix(secret)} was refused (HTTP ${status}).`).message;
92
111
  }
93
112
  function reportSessionCommandRefused(secret, consequence) {
@@ -861,6 +880,7 @@ async function buildHandler(entryInput, config, options) {
861
880
  "worker",
862
881
  "browser"
863
882
  ],
883
+ external: declaredModules().flatMap((module) => module.specifiers),
864
884
  target: "es2022",
865
885
  treeShaking: true,
866
886
  minify: true,
@@ -909,7 +929,7 @@ async function buildHandler(entryInput, config, options) {
909
929
  * that has to name this version — `--version`, the dependency a scaffolded
910
930
  * project pins — reads it here, so a release bumps one file.
911
931
  */
912
- const CLI_VERSION = "0.21.1";
932
+ const CLI_VERSION = "0.22.0";
913
933
  //#endregion
914
934
  //#region src/cli-login.ts
915
935
  const START_PATH = "/v1/cli/login/start";
@@ -1592,8 +1612,15 @@ function planLimitAdvice(reason) {
1592
1612
  //#region src/report.ts
1593
1613
  /** Where a value sits in the deploy's summary block, so every line under one label agrees. */
1594
1614
  const VALUE_COLUMN = " ".repeat(14);
1595
- /** The platform's own prose, kept inside the block it is printed in. */
1615
+ /**
1616
+ * The platform's own prose, kept inside the block it is printed in. A line
1617
+ * break it already carries stays one, which keeps a refusal's trace id on a
1618
+ * line of its own.
1619
+ */
1596
1620
  function wrapped(said) {
1621
+ return said.split("\n").flatMap(wrappedLine);
1622
+ }
1623
+ function wrappedLine(said) {
1597
1624
  const width = 64;
1598
1625
  const lines = [];
1599
1626
  let line = "";
@@ -2034,6 +2061,13 @@ function attachedDomainLines(domain, printedBy = "deploy") {
2034
2061
  `${VALUE_COLUMN}Remove 'domain' from wawesome-function.json, or deploy again`,
2035
2062
  `${VALUE_COLUMN}once it is gone to attach it afresh.`
2036
2063
  ];
2064
+ if (domain.withdrawn) return [
2065
+ "",
2066
+ ` Domain: \x1b[33m${hostname}: stopped, because the plan does not include custom domains\x1b[0m`,
2067
+ `${VALUE_COLUMN}It is still attached, and its records and certificate are kept.`,
2068
+ `${VALUE_COLUMN}Move to a paid plan and it answers again within the hour.`,
2069
+ `${VALUE_COLUMN}This app's own address still answers.`
2070
+ ];
2037
2071
  const legs = domain.legs ?? [];
2038
2072
  if (legs.length > 1) return pairLines(domain, legs, printedBy);
2039
2073
  const lines = ["", ` Domain: \x1b[36m${hostname}\x1b[0m: ${whereItGotTo(domain)}`];
@@ -2327,22 +2361,26 @@ function sha256(bytes) {
2327
2361
  }
2328
2362
  /**
2329
2363
  * 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.
2364
+ * asset path and content-hash pairs, the same set for its **Private file**s
2365
+ * marked apart, and the **Fallback document** it declares.
2331
2366
  *
2332
2367
  * Computed here, before anything is uploaded, so a deploy that changed nothing
2333
2368
  * is refused before a byte moves. Mirrors `deploy_digest` in
2334
2369
  * `server/crates/core/src/features/assets/identity.rs` — the gateway recomputes
2335
2370
  * it from what actually arrives, so the two have to agree exactly.
2336
2371
  *
2337
- * A deploy declaring no fallback contributes nothing here rather than a stated
2338
- * absence, so every digest computed before fallbacks existed is unmoved.
2372
+ * A deploy declaring no fallback and no private files contributes nothing here
2373
+ * for either rather than a stated absence, so every digest computed before
2374
+ * they existed is unmoved.
2339
2375
  */
2340
2376
  const DEPLOY_DIGEST_DOMAIN = "wawesome-deploy-v1";
2341
- function deployDigest(bundle, assets, fallback) {
2377
+ function deployDigest(bundle, assets, fallback, privateFiles = []) {
2378
+ const byPath = (files) => [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
2342
2379
  const digest = crypto.createHash("sha256");
2343
2380
  digest.update(`${DEPLOY_DIGEST_DOMAIN}\n`);
2344
2381
  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`);
2382
+ for (const asset of byPath(assets)) digest.update(`${asset.path}\0${asset.content_hash}\n`);
2383
+ for (const file of byPath(privateFiles)) digest.update(`private\0${file.path}\0${file.content_hash}\n`);
2346
2384
  if (fallback) digest.update(`fallback\0${fallback.path}\0${fallback.status}\n`);
2347
2385
  return digest.digest("hex");
2348
2386
  }
@@ -3329,6 +3367,7 @@ async function deploy(entryInput, options) {
3329
3367
  const declaresSchedules = config.schedules !== void 0;
3330
3368
  const declaresFallback = declared.fallback !== void 0;
3331
3369
  const assets = config.assets ? collectAssets(path.resolve(config.assets)) : [];
3370
+ const privateFiles = config.files ? collectAssets(path.resolve(config.files)) : [];
3332
3371
  const documents = assets.filter((asset) => isDocument(asset.path));
3333
3372
  if (jsCode === null && documents.length === 0) {
3334
3373
  console.error(`[wawesome] Error: Entry file '${DEFAULT_ENTRY}' not found, and this project carries no pages.`);
@@ -3337,7 +3376,10 @@ async function deploy(entryInput, options) {
3337
3376
  console.error("[wawesome] \x1B[36m\"assets\"\x1B[0m at a directory holding an index.html.");
3338
3377
  process.exit(1);
3339
3378
  }
3340
- if (assets.length > 0) await uploadAssets(creds, app, funcName, jsCode ?? "", assets, declared, isVerbose, declaresSchedules);
3379
+ if (assets.length > 0 || privateFiles.length > 0) await uploadAssets(creds, app, funcName, jsCode ?? "", {
3380
+ assets,
3381
+ privateFiles
3382
+ }, declared, isVerbose, declaresSchedules);
3341
3383
  console.log(`[wawesome] Uploading code for ${app}/${funcName}...`);
3342
3384
  const uploadUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/code`;
3343
3385
  if (isVerbose) console.log(`[wawesome:verbose] POST ${uploadUrl}`);
@@ -3345,7 +3387,7 @@ async function deploy(entryInput, options) {
3345
3387
  ...declaredFields(declared),
3346
3388
  ...declared.message ? { message: declared.message } : {}
3347
3389
  };
3348
- const uploadRes = assets.length > 0 || declaresSchedules || declaresFallback || Object.keys(declaredOnTheWire).length > 0 || jsCode === null ? await authorizedFetch(uploadUrl, {
3390
+ const uploadRes = assets.length > 0 || privateFiles.length > 0 || declaresSchedules || declaresFallback || Object.keys(declaredOnTheWire).length > 0 || jsCode === null ? await authorizedFetch(uploadUrl, {
3349
3391
  method: "POST",
3350
3392
  headers: {
3351
3393
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -3354,6 +3396,7 @@ async function deploy(entryInput, options) {
3354
3396
  body: JSON.stringify({
3355
3397
  ...jsCode !== null ? { code: jsCode } : {},
3356
3398
  ...assets.length > 0 ? { assets: manifestOf(assets) } : {},
3399
+ ...privateFiles.length > 0 ? { files: manifestOf(privateFiles) } : {},
3357
3400
  ...declaresSchedules ? { schedules: config.schedules } : {},
3358
3401
  ...declaresFallback ? { fallback: declared.fallback } : {},
3359
3402
  ...declaredOnTheWire
@@ -3461,6 +3504,7 @@ async function deploy(entryInput, options) {
3461
3504
  if (version !== void 0) console.log(` Version: ${version}${nothingBuilt ? " (already live, nothing promoted)" : ""}`);
3462
3505
  for (const line of handlerLines(bundlePath)) console.log(line);
3463
3506
  if (assets.length > 0) console.log(` Assets: ${assets.length}${pageCount(documents.length)}`);
3507
+ if (privateFiles.length > 0) console.log(` Private: ${privateFiles.length}`);
3464
3508
  console.log(` Visibility: ${visibility}`);
3465
3509
  if (schedules.length > 0) {
3466
3510
  console.log("");
@@ -3510,7 +3554,7 @@ function pageCount(pages) {
3510
3554
  * The identity of the whole deploy goes with the question, so a redeploy that
3511
3555
  * changed nothing at all is refused here — before a byte of it has moved.
3512
3556
  */
3513
- async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVerbose, continueWhenUnchanged) {
3557
+ async function uploadAssets(creds, app, funcName, bundle, { assets, privateFiles }, declared, isVerbose, continueWhenUnchanged) {
3514
3558
  const manifestRes = await authorizedFetch(`${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/assets/manifest`, {
3515
3559
  method: "POST",
3516
3560
  headers: {
@@ -3518,8 +3562,9 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
3518
3562
  "Content-Type": "application/json"
3519
3563
  },
3520
3564
  body: JSON.stringify({
3521
- deploy_digest: deployDigest(Buffer.from(bundle, "utf-8"), assets, declared.fallback),
3565
+ deploy_digest: deployDigest(Buffer.from(bundle, "utf-8"), assets, declared.fallback, privateFiles),
3522
3566
  assets: manifestOf(assets),
3567
+ ...privateFiles.length > 0 ? { files: manifestOf(privateFiles) } : {},
3523
3568
  ...declaredFields(declared)
3524
3569
  })
3525
3570
  });
@@ -3546,12 +3591,13 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
3546
3591
  process.exit(1);
3547
3592
  }
3548
3593
  const { missing = [] } = await manifestRes.json();
3549
- const toUpload = assets.filter((asset) => missing.includes(asset.content_hash));
3594
+ const carried = [...assets, ...privateFiles];
3595
+ const toUpload = [...new Map(carried.filter((file) => missing.includes(file.content_hash)).map((file) => [file.content_hash, file])).values()];
3550
3596
  if (toUpload.length === 0) {
3551
- console.log(`[wawesome] ✅ ${assets.length} asset(s) already on the platform, nothing to upload.`);
3597
+ console.log(`[wawesome] ✅ ${carried.length} file(s) already on the platform, nothing to upload.`);
3552
3598
  return;
3553
3599
  }
3554
- console.log(`[wawesome] Uploading ${toUpload.length} of ${assets.length} asset(s)...`);
3600
+ console.log(`[wawesome] Uploading ${toUpload.length} of ${carried.length} file(s)...`);
3555
3601
  for (const asset of toUpload) {
3556
3602
  if (isVerbose) console.log(`[wawesome:verbose] PUT ${asset.path} (${asset.size_bytes} bytes)`);
3557
3603
  const res = await authorizedFetch(`${creds.gateway_url}/v1/assets/${asset.content_hash}`, () => ({
@@ -3574,7 +3620,7 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
3574
3620
  process.exit(1);
3575
3621
  }
3576
3622
  }
3577
- console.log(`[wawesome] ✅ ${toUpload.length} asset(s) uploaded.`);
3623
+ console.log(`[wawesome] ✅ ${toUpload.length} file(s) uploaded.`);
3578
3624
  }
3579
3625
  //#endregion
3580
3626
  //#region src/opens-host.ts
@@ -4438,7 +4484,8 @@ async function promptForDeclaredEnv(session, declared) {
4438
4484
  console.log(` \x1b[1m${variable.key}\x1b[0m \x1b[90m(${flags.join(", ")})\x1b[0m`);
4439
4485
  console.log(` ${variable.description}`);
4440
4486
  console.log(` \x1b[90mWhere to find it: ${variable.where_to_find}\x1b[0m`);
4441
- const value = await session.ask(` ${variable.key}?`, "");
4487
+ const later = variable.required ? " (leave blank to set it after the deploy)" : "";
4488
+ const value = await session.ask(` ${variable.key}?${later}`, "");
4442
4489
  console.log("");
4443
4490
  answers.push({
4444
4491
  declared: variable,
@@ -5111,6 +5158,20 @@ async function fetchInvocationLogBody(gatewayUrl, tenantJwt, invocationId, isVer
5111
5158
  }
5112
5159
  const body = await res.text();
5113
5160
  process.stdout.write(body.endsWith("\n") ? body : body + "\n");
5161
+ const remediation = await fetchRemediation(gatewayUrl, tenantJwt, invocationId);
5162
+ if (remediation?.cause === "egress-blocked") console.log(`[wawesome] An outbound call was blocked (egress-blocked). Open its host under Egress: ${remediation.dashboard_url}`);
5163
+ }
5164
+ async function fetchRemediation(gatewayUrl, tenantJwt, invocationId) {
5165
+ try {
5166
+ const res = await authorizedFetch(`${gatewayUrl}/v1/invocations/${encodeURIComponent(invocationId)}`, {
5167
+ method: "GET",
5168
+ headers: { Authorization: `Bearer ${tenantJwt}` }
5169
+ });
5170
+ if (!res.ok) return void 0;
5171
+ return (await res.json()).remediation;
5172
+ } catch {
5173
+ return;
5174
+ }
5114
5175
  }
5115
5176
  /**
5116
5177
  * List recent invocations for a function.
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as unsupportedGlobals, n as methodRemedy, r as polyfilledGlobals, t as declaredMethods } from "./guest-surface-D5X-IMAN.mjs";
1
+ import { i as polyfilledGlobals, o as unsupportedGlobals, r as methodRemedy, t as declaredMethods } from "./guest-surface-BwrNP2Cz.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  //#region src/vite.ts
@@ -1,4 +1,4 @@
1
- import { t as applyGuestParity } from "./guest-parity-W61MH7Fb.mjs";
1
+ import { t as applyGuestParity } from "./guest-parity-DA7x-8BC.mjs";
2
2
  //#region src/vitest-setup.ts
3
3
  applyGuestParity();
4
4
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.21.1",
3
+ "version": "0.22.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"