wawesome 0.4.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +88 -1
  2. package/dist/index.mjs +152 -45
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -258,6 +258,92 @@ Two rules to know about:
258
258
  **HTML is refused at deploy time.** Your Function renders its own markup, and a document served from
259
259
  your App's own origin is the sharpest same-origin vector a static file has.
260
260
 
261
+ **An SVG is served with script denied.** The rule behind the refusal above is that nothing you deploy
262
+ as a file runs script on your App's own origin, and an SVG opened directly in a browser would. It is
263
+ served rather than refused because denying it costs the file nothing: an `<img src="logo.svg">` never
264
+ ran that script, so your drawings render as they always did — every SVG and XML file carries
265
+ `Content-Security-Policy: script-src 'none'`, and only navigating straight to one loses anything.
266
+
267
+ ### Schedules
268
+
269
+ Add `"schedules"` to run a Function on a recurring timer, with no caller:
270
+
271
+ ```json
272
+ {
273
+ "app": "my-app",
274
+ "function": "nightly-reconcile",
275
+ "entry": "src/index.ts",
276
+ "schedules": [{ "name": "overnight", "expression": "0 3 * * *" }]
277
+ }
278
+ ```
279
+
280
+ An expression is **five fields, read in UTC** — minute, hour, day of month, month, day of week. There
281
+ is no seconds field, and no timezone: a local zone would make one night a year fire a job twice and
282
+ another night not at all.
283
+
284
+ The name is yours to choose and is what the platform keys the schedule by, so editing an expression
285
+ is a change to the same schedule rather than the deletion of one and the creation of another — its
286
+ history and its paused state stay attached to it.
287
+
288
+ Your deploy applies them and prints each one with the time it will next run. It is refused, before
289
+ anything is built or uploaded, if an expression cannot be read, if it would run more often than every
290
+ five minutes, or if one Function declares more than five schedules.
291
+
292
+ Two rules worth knowing before you edit the file:
293
+
294
+ - **A schedule you delete from the file is disabled, not deleted.** Its history stays, and declaring
295
+ it again is what turns it back on — so a typo costs you a deploy rather than a job's record.
296
+ - **A deploy never resumes a schedule a person paused.** When it runs is code; whether it is running
297
+ is not, and an unrelated commit the next morning must not restart what you stopped at 3am.
298
+
299
+ Leaving `schedules` out of the file entirely says nothing about them and changes nothing. Writing
300
+ `"schedules": []` says this Function declares none, which disables the ones it used to have.
301
+
302
+ ### Keeping a Function off the web
303
+
304
+ A job on a timer should not also be sitting at a guessable URL where a stranger can fire it. Add
305
+ `"visibility"` to make a Function unreachable from the internet:
306
+
307
+ ```json
308
+ {
309
+ "app": "my-app",
310
+ "function": "nightly-reconcile",
311
+ "entry": "src/index.ts",
312
+ "visibility": "private"
313
+ }
314
+ ```
315
+
316
+ A private Function has **no public address at all** in production — not a hidden one, not one
317
+ behind a credential. A request for it against your App's own hostname is answered with the same 404
318
+ as a Function that was never deployed. This is how you deploy a job with side effects without leaving
319
+ it where a stranger who guesses the slug can fire it.
320
+
321
+ Leave the line out and your Function is public, which is what every Function without it has always
322
+ been.
323
+
324
+ On your own machine, the local development surface serves Functions regardless of visibility, and
325
+ honours an explicit `x-wawesome-trigger` header so you can exercise a scheduled run by hand:
326
+
327
+ ```bash
328
+ # Exercise a private or scheduled function locally on the development path form:
329
+ curl -X POST http://localhost:3000/x/my-tenant-slug/default-app/nightly-reconcile \
330
+ -H "x-wawesome-trigger: schedule"
331
+ ```
332
+
333
+ Because production strips the reserved header namespace inbound, a request that works locally
334
+ provably cannot work against the Public address.
335
+
336
+ Making a Function private takes nothing but the deploy. Making it public again does not: deleting
337
+ the line is refused, and the deploy tells you so having written nothing.
338
+
339
+ ```bash
340
+ npx wawesome deploy --publish
341
+ ```
342
+
343
+ That is deliberate — the line that keeps a job off the internet is one line, and a deploy that
344
+ quietly honoured its deletion would put the job back on the open internet with nothing said. Every
345
+ deploy prints the visibility it landed, beside the URL or in place of it.
346
+
261
347
  ### Reserved headers
262
348
 
263
349
  `x-wawesome-*` belongs to the platform in both directions. It is stripped off the request before your
@@ -265,11 +351,12 @@ handler sees it, and off your response before the caller does — so **do not na
265
351
  on that prefix**: it is dropped silently rather than rejected, and you will not get an error telling
266
352
  you why it vanished.
267
353
 
268
- Three headers arrive or leave on it, and the stripping is what makes them worth trusting:
354
+ Four headers arrive or leave on it, and the stripping is what makes them worth trusting:
269
355
 
270
356
  | Header | Direction | What it means |
271
357
  | --- | --- | --- |
272
358
  | `x-wawesome-forwarded-prefix` | inbound | The mount that was stripped from the path. Join it to the path you observe to rebuild the caller's URL. |
359
+ | `x-wawesome-trigger` | inbound | How this run started: `caller` when someone called your address, `schedule` when fired by a Schedule. A caller cannot forge it in production (stripped inbound). On the local development surface, pass `x-wawesome-trigger: schedule` to exercise a background run by hand with the collapsed budget. |
273
360
  | `x-wawesome-invocation-id` | outbound | The id of this run — the key to fetch its logs with `npx wawesome logs --invocation <id>`. |
274
361
  | `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. |
275
362
 
package/dist/index.mjs CHANGED
@@ -759,7 +759,7 @@ async function buildJs(entryInput, options) {
759
759
  * that has to name this version — `--version`, the dependency a scaffolded
760
760
  * project pins — reads it here, so a release bumps one file.
761
761
  */
762
- const CLI_VERSION = "0.4.0";
762
+ const CLI_VERSION = "0.6.0";
763
763
  //#endregion
764
764
  //#region src/prompt.ts
765
765
  /**
@@ -1155,6 +1155,42 @@ async function whoami() {
1155
1155
  console.log(` Gateway: ${creds.gateway_url}\n`);
1156
1156
  }
1157
1157
  //#endregion
1158
+ //#region src/schedules.ts
1159
+ /**
1160
+ * When a schedule next runs, or why it does not run at all.
1161
+ *
1162
+ * The three ways of being off never render alike: someone who cannot tell a
1163
+ * pause from a schedule they deleted from their config file will press resume
1164
+ * and watch nothing happen.
1165
+ */
1166
+ function whenItRuns(schedule) {
1167
+ switch (schedule.state) {
1168
+ case "active": return schedule.next_fire_at ? `next run ${asUtc(schedule.next_fire_at)}` : "next run unknown";
1169
+ case "paused": return "paused — resume it to run again";
1170
+ case "undeclared": return "not in this config file — disabled, kept";
1171
+ case "suspended": return "suspended — the workspace owes payment";
1172
+ default: return schedule.state;
1173
+ }
1174
+ }
1175
+ /** `2026-08-23 03:00 UTC`, which is the zone every expression is read in. */
1176
+ function asUtc(iso) {
1177
+ const at = new Date(iso);
1178
+ if (Number.isNaN(at.getTime())) return iso;
1179
+ const pad = (n) => String(n).padStart(2, "0");
1180
+ return `${at.getUTCFullYear()}-${pad(at.getUTCMonth() + 1)}-${pad(at.getUTCDate())} ${pad(at.getUTCHours())}:${pad(at.getUTCMinutes())} UTC`;
1181
+ }
1182
+ /**
1183
+ * The block a deploy prints for the schedules it just applied. Every schedule
1184
+ * appears, including the ones that are off, so putting recurring compute on the
1185
+ * bill — or taking it off — is never silent.
1186
+ */
1187
+ function scheduleLines(schedules) {
1188
+ if (schedules.length === 0) return [];
1189
+ const nameWidth = Math.max(...schedules.map((s) => s.name.length));
1190
+ const expressionWidth = Math.max(...schedules.map((s) => s.expression.length));
1191
+ return [" Schedules:", ...[...schedules].sort((a, b) => a.name.localeCompare(b.name)).map((schedule) => ` ${schedule.name.padEnd(nameWidth)} ${schedule.expression.padEnd(expressionWidth)} ${whenItRuns(schedule)}`)];
1192
+ }
1193
+ //#endregion
1158
1194
  //#region src/usage.ts
1159
1195
  const USAGE_TIMEOUT_MS = 2e3;
1160
1196
  /**
@@ -1323,9 +1359,15 @@ function billingPageUrl() {
1323
1359
  return `${base}/billing`;
1324
1360
  }
1325
1361
  }
1362
+ /** Refusals whose remedy is on the billing page and nowhere else. */
1363
+ const PLAN_LIMITS = [
1364
+ "app-slots-exhausted",
1365
+ "storage-exhausted",
1366
+ "paid-plan-required"
1367
+ ];
1326
1368
  /** The rule itself is the gateway's prose, and is deliberately not restated here. */
1327
1369
  function planLimitAdvice(reason) {
1328
- if (reason !== "app-slots-exhausted" && reason !== "storage-exhausted") return "";
1370
+ if (!reason || !PLAN_LIMITS.includes(reason)) return "";
1329
1371
  return `Where to resolve it: ${billingPageUrl()}`;
1330
1372
  }
1331
1373
  //#endregion
@@ -1472,6 +1514,31 @@ function trimTrailingSlashes(origin) {
1472
1514
  }
1473
1515
  //#endregion
1474
1516
  //#region src/deploy.ts
1517
+ function declaredFields(declared) {
1518
+ return {
1519
+ ...declared.visibility ? { visibility: declared.visibility } : {},
1520
+ ...declared.confirmPublish ? { confirm_publish: true } : {}
1521
+ };
1522
+ }
1523
+ /**
1524
+ * The visibility this project declares, refused here rather than at the gateway
1525
+ * so a typo is a message at the keyboard.
1526
+ */
1527
+ function declaredVisibility(value) {
1528
+ if (value === void 0 || value === null) return void 0;
1529
+ if (value === "public" || value === "private") return value;
1530
+ console.error(`[wawesome] Error: 'visibility' in wawesome-function.json is '${String(value)}'.`);
1531
+ console.error("[wawesome] It must be \"public\" or \"private\".");
1532
+ process.exit(1);
1533
+ }
1534
+ function reportPublishRefusal(message) {
1535
+ console.error(`\n[wawesome] \x1b[31mError: ${message}\x1b[0m`);
1536
+ console.error("[wawesome] Nothing was deployed. This Function is private, so its address");
1537
+ console.error("[wawesome] resolves for nobody — publishing it puts it back on the open");
1538
+ console.error("[wawesome] internet.");
1539
+ console.error("[wawesome] Re-run with \x1B[36mwawesome deploy --publish\x1B[0m to publish it, or put");
1540
+ console.error("[wawesome] \x1B[36m\"visibility\": \"private\"\x1B[0m back in wawesome-function.json.\n");
1541
+ }
1475
1542
  /**
1476
1543
  * Deploy a function: build → upload JS to gateway → promote.
1477
1544
  */
@@ -1489,6 +1556,10 @@ async function deploy(entryInput, options) {
1489
1556
  process.exit(1);
1490
1557
  }
1491
1558
  const { app, function: funcName } = config;
1559
+ const declared = {
1560
+ visibility: declaredVisibility(config.visibility),
1561
+ confirmPublish: Boolean(options.publish)
1562
+ };
1492
1563
  if (isVerbose) {
1493
1564
  console.log(`[wawesome:verbose] Deploying to app=${app}, function=${funcName}`);
1494
1565
  console.log(`[wawesome:verbose] Gateway: ${creds.gateway_url}`);
@@ -1520,12 +1591,14 @@ async function deploy(entryInput, options) {
1520
1591
  for (const line of surfaceReportLines(findings)) (refused ? console.error : console.warn)(line);
1521
1592
  if (refused) process.exit(1);
1522
1593
  }
1594
+ const declaresSchedules = config.schedules !== void 0;
1523
1595
  const assets = config.assets ? collectAssets(path.resolve(config.assets)) : [];
1524
- if (assets.length > 0) await uploadAssets(creds, app, funcName, jsCode, assets, isVerbose);
1596
+ if (assets.length > 0) await uploadAssets(creds, app, funcName, jsCode, assets, declared, isVerbose, declaresSchedules);
1525
1597
  console.log(`[wawesome] Uploading code for ${app}/${funcName}...`);
1526
1598
  const uploadUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/code`;
1527
1599
  if (isVerbose) console.log(`[wawesome:verbose] POST ${uploadUrl}`);
1528
- const uploadRes = assets.length > 0 ? await fetch(uploadUrl, {
1600
+ const declaredOnTheWire = declaredFields(declared);
1601
+ const uploadRes = assets.length > 0 || declaresSchedules || Object.keys(declaredOnTheWire).length > 0 ? await fetch(uploadUrl, {
1529
1602
  method: "POST",
1530
1603
  headers: {
1531
1604
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -1533,7 +1606,9 @@ async function deploy(entryInput, options) {
1533
1606
  },
1534
1607
  body: JSON.stringify({
1535
1608
  code: jsCode,
1536
- assets: manifestOf(assets)
1609
+ ...assets.length > 0 ? { assets: manifestOf(assets) } : {},
1610
+ ...declaresSchedules ? { schedules: config.schedules } : {},
1611
+ ...declaredOnTheWire
1537
1612
  })
1538
1613
  }) : await fetch(uploadUrl, {
1539
1614
  method: "POST",
@@ -1545,9 +1620,16 @@ async function deploy(entryInput, options) {
1545
1620
  });
1546
1621
  if (!uploadRes.ok) {
1547
1622
  const errorBody = await uploadRes.text();
1623
+ const refusal = rejectionOf(errorBody, uploadRes.status, "Version with this code bundle already exists.");
1548
1624
  if (uploadRes.status === 401) console.error("[wawesome] Error: Authentication expired. Run 'wawesome login' again.");
1549
- else if (uploadRes.status === 409) {
1550
- const refusal = rejectionOf(errorBody, uploadRes.status, "Version with this code bundle already exists.");
1625
+ else if (refusal.reason === "paid-plan-required") {
1626
+ console.error(`\n[wawesome] \x1b[31mError: ${refusal.message}\x1b[0m`);
1627
+ console.error(`[wawesome] ${planLimitAdvice(refusal.reason)}\n`);
1628
+ } else if (uploadRes.status === 409) {
1629
+ if (refusal.reason === "publish-needs-confirmation") {
1630
+ reportPublishRefusal(refusal.message);
1631
+ process.exit(1);
1632
+ }
1551
1633
  console.error(`\n[wawesome] \x1b[31mError: ${refusal.message}\x1b[0m`);
1552
1634
  console.error("[wawesome] Code versions are immutable and cannot be overwritten.");
1553
1635
  console.error("[wawesome] To switch active version, run: \x1B[36mwawesome version switch\x1B[0m\n");
@@ -1557,33 +1639,40 @@ async function deploy(entryInput, options) {
1557
1639
  }
1558
1640
  process.exit(1);
1559
1641
  }
1560
- const version = (await uploadRes.json()).version_number;
1561
- console.log(`[wawesome] ✅ Code uploaded (version ${version ?? "unknown"}).`);
1562
- console.log(`[wawesome] Promoting to production...`);
1563
- const deployUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/deploy`;
1564
- if (isVerbose) console.log(`[wawesome:verbose] POST ${deployUrl}`);
1565
- const deployBody = {};
1566
- if (version !== void 0) deployBody.version_number = version;
1567
- const deployRes = await fetch(deployUrl, {
1568
- method: "POST",
1569
- headers: {
1570
- Authorization: `Bearer ${creds.tenant_jwt}`,
1571
- "Content-Type": "application/json"
1572
- },
1573
- body: JSON.stringify(deployBody)
1574
- });
1575
- if (!deployRes.ok) {
1576
- const errorBody = await deployRes.text();
1577
- if (deployRes.status === 401) console.error("[wawesome] Error: Authentication expired. Run 'wawesome login' again.");
1578
- else {
1579
- console.error(`[wawesome] Error: Deployment failed (HTTP ${deployRes.status}).`);
1580
- if (isVerbose) console.error(`[wawesome:verbose] Response: ${errorBody}`);
1642
+ const uploadData = await uploadRes.json();
1643
+ const version = uploadData.version_number;
1644
+ const schedules = uploadData.schedules ?? [];
1645
+ const visibility = uploadData.visibility ?? declared.visibility ?? "public";
1646
+ const nothingBuilt = uploadData.status === "declarations-updated";
1647
+ if (nothingBuilt) console.log(`[wawesome] ✅ Code and files unchanged (version ${version ?? "unknown"}); what the config file declares applied.`);
1648
+ else console.log(`[wawesome] Code uploaded (version ${version ?? "unknown"}).`);
1649
+ if (!nothingBuilt) {
1650
+ console.log(`[wawesome] Promoting to production...`);
1651
+ const deployUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/deploy`;
1652
+ if (isVerbose) console.log(`[wawesome:verbose] POST ${deployUrl}`);
1653
+ const deployBody = {};
1654
+ if (version !== void 0) deployBody.version_number = version;
1655
+ const deployRes = await fetch(deployUrl, {
1656
+ method: "POST",
1657
+ headers: {
1658
+ Authorization: `Bearer ${creds.tenant_jwt}`,
1659
+ "Content-Type": "application/json"
1660
+ },
1661
+ body: JSON.stringify(deployBody)
1662
+ });
1663
+ if (!deployRes.ok) {
1664
+ const errorBody = await deployRes.text();
1665
+ if (deployRes.status === 401) console.error("[wawesome] Error: Authentication expired. Run 'wawesome login' again.");
1666
+ else {
1667
+ console.error(`[wawesome] Error: Deployment failed (HTTP ${deployRes.status}).`);
1668
+ if (isVerbose) console.error(`[wawesome:verbose] Response: ${errorBody}`);
1669
+ }
1670
+ process.exit(1);
1581
1671
  }
1582
- process.exit(1);
1583
1672
  }
1584
1673
  let address = null;
1585
1674
  let surface = null;
1586
- try {
1675
+ if (visibility === "public") try {
1587
1676
  const { slug } = await resolveWorkspace(creds);
1588
1677
  surface = await fetchInvocationSurface(creds.gateway_url);
1589
1678
  address = publicAddress(surface, slug, app, funcName);
@@ -1597,19 +1686,27 @@ async function deploy(entryInput, options) {
1597
1686
  if (isVerbose) console.log(`[wawesome:verbose] Could not read the plan's usage: ${errorText(err)}`);
1598
1687
  }
1599
1688
  console.log("\n======================================================");
1600
- console.log("🚀 \x1B[32mDEPLOYED SUCCESSFULLY!\x1B[0m");
1689
+ console.log(nothingBuilt ? "🕒 \x1B[32mCONFIGURATION APPLIED\x1B[0m" : "🚀 \x1B[32mDEPLOYED SUCCESSFULLY!\x1B[0m");
1601
1690
  console.log("======================================================");
1602
- console.log(`\n App: ${app}`);
1603
- console.log(` Function: ${funcName}`);
1604
- if (version !== void 0) console.log(` Version: ${version}`);
1605
- if (assets.length > 0) console.log(` Assets: ${assets.length}`);
1606
- if (address) {
1607
- console.log(`\n URL: \x1b[36m${address}\x1b[0m`);
1608
- console.log(` ${SUBTREE_NOTE}`);
1691
+ console.log(`\n App: ${app}`);
1692
+ console.log(` Function: ${funcName}`);
1693
+ if (version !== void 0) console.log(` Version: ${version}${nothingBuilt ? " (already live, nothing promoted)" : ""}`);
1694
+ if (assets.length > 0) console.log(` Assets: ${assets.length}`);
1695
+ console.log(` Visibility: ${visibility}`);
1696
+ if (schedules.length > 0) {
1697
+ console.log("");
1698
+ for (const line of scheduleLines(schedules)) console.log(line);
1699
+ }
1700
+ if (visibility === "private") {
1701
+ console.log("\n URL: none — this Function is private, so it is not reachable");
1702
+ console.log(" from the web at all.");
1703
+ } else if (address) {
1704
+ console.log(`\n URL: \x1b[36m${address}\x1b[0m`);
1705
+ console.log(` ${SUBTREE_NOTE}`);
1609
1706
  } else if (surface) {
1610
- console.log("\n URL: none — this gateway serves no public address form.");
1611
- console.log(" Set CONTENT_ORIGIN on it, or ALLOW_PATH_INVOCATION_FORM");
1612
- console.log(" for local development.");
1707
+ console.log("\n URL: none — this gateway serves no public address form.");
1708
+ console.log(" Set CONTENT_ORIGIN on it, or ALLOW_PATH_INVOCATION_FORM");
1709
+ console.log(" for local development.");
1613
1710
  }
1614
1711
  if (headroom) {
1615
1712
  console.log("");
@@ -1620,7 +1717,8 @@ async function deploy(entryInput, options) {
1620
1717
  app,
1621
1718
  functionName: funcName,
1622
1719
  version,
1623
- address
1720
+ address,
1721
+ schedules
1624
1722
  };
1625
1723
  }
1626
1724
  /**
@@ -1630,7 +1728,7 @@ async function deploy(entryInput, options) {
1630
1728
  * The identity of the whole deploy goes with the question, so a redeploy that
1631
1729
  * changed nothing at all is refused here — before a byte of it has moved.
1632
1730
  */
1633
- async function uploadAssets(creds, app, funcName, bundle, assets, isVerbose) {
1731
+ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVerbose, continueWhenUnchanged) {
1634
1732
  const manifestUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/assets/manifest`;
1635
1733
  const manifestRes = await fetch(manifestUrl, {
1636
1734
  method: "POST",
@@ -1640,14 +1738,23 @@ async function uploadAssets(creds, app, funcName, bundle, assets, isVerbose) {
1640
1738
  },
1641
1739
  body: JSON.stringify({
1642
1740
  deploy_digest: deployDigest(Buffer.from(bundle, "utf-8"), assets),
1643
- assets: manifestOf(assets)
1741
+ assets: manifestOf(assets),
1742
+ ...declaredFields(declared)
1644
1743
  })
1645
1744
  });
1646
1745
  if (!manifestRes.ok) {
1647
1746
  const errorBody = await manifestRes.text();
1648
1747
  if (manifestRes.status === 401) console.error("[wawesome] Error: Authentication expired. Run 'wawesome login' again.");
1649
1748
  else if (manifestRes.status === 409) {
1749
+ if (continueWhenUnchanged) {
1750
+ console.log("[wawesome] Code and files unchanged since the last deploy; applying schedules.");
1751
+ return;
1752
+ }
1650
1753
  const refusal = rejectionOf(errorBody, manifestRes.status, "This deploy is already deployed.");
1754
+ if (refusal.reason === "publish-needs-confirmation") {
1755
+ reportPublishRefusal(refusal.message);
1756
+ process.exit(1);
1757
+ }
1651
1758
  console.error(`\n[wawesome] \x1b[31mError: ${refusal.message}\x1b[0m`);
1652
1759
  console.error("[wawesome] Nothing changed since the last deploy, so nothing was uploaded.\n");
1653
1760
  } else {
@@ -3595,7 +3702,7 @@ async function workspaceCommand(action, target, options) {
3595
3702
  //#region src/index.ts
3596
3703
  const cli = cac("wawesome");
3597
3704
  cli.command("build [entry]", "Bundle a serverless function to an optimized JS file").option("-o, --out <path>", "Output JS bundle path", { default: "dist/index.js" }).option("-v, --verbose", "Enable verbose debug output").action((entry, options) => buildJs(entry, options));
3598
- cli.command("deploy [entry]", "Build, upload, and promote a serverless function").option("-o, --out <path>", "Output JS bundle path", { default: "dist/index.js" }).option("-v, --verbose", "Enable verbose debug output").option("--skip-build", "Skip the build step, deploy an already-built bundle").action((entry, options) => deploy(entry, options));
3705
+ cli.command("deploy [entry]", "Build, upload, and promote a serverless function").option("-o, --out <path>", "Output JS bundle path", { default: "dist/index.js" }).option("-v, --verbose", "Enable verbose debug output").option("--skip-build", "Skip the build step, deploy an already-built bundle").option("--publish", "Confirm putting a private function back on its public address").action((entry, options) => deploy(entry, options));
3599
3706
  cli.command("version [action] [target]", "Manage versions (e.g. 'version list', 'version switch [version]')").option("-e, --env <environment>", "Target environment (default: production)").option("-v, --verbose", "Enable verbose debug output").action((action, target, options) => {
3600
3707
  if (!action || action === "list" || action === "ls") return listVersions(options);
3601
3708
  if (action === "switch" || action === "use" || action === "select") return switchVersion(target, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {