wawesome 0.0.8 → 0.0.9

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 +7 -0
  2. package/dist/index.mjs +120 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -67,6 +67,13 @@ dlx`, `bun x`), so the types are there when you open the project and the bundler
67
67
  template's imports. Pass `--no-install` to do it yourself. A failed install never stops the flow;
68
68
  the command to re-run is printed.
69
69
 
70
+ Before any of that is sent anywhere, the CLI shows you the **public URL your Function will have**
71
+ and offers to change your workspace address — the part of that URL that is yours rather than this
72
+ project's, and the same one `wawesome workspace show` prints. It is randomly minted at signup and
73
+ stays changeable until your first deploy, at which point it locks for good, because live URLs carry
74
+ it. Press enter to keep it. If it is already locked, the offer is not made and the reason is said
75
+ plainly.
76
+
70
77
  Being logged in is checked **before** the first question, not at the deploy: an expired session
71
78
  offers you a login there and then, and declining still leaves you the project plus the two commands
72
79
  that finish it.
package/dist/index.mjs CHANGED
@@ -161,7 +161,7 @@ async function buildJs(entryInput, options) {
161
161
  * that has to name this version — `--version`, the dependency a scaffolded
162
162
  * project pins — reads it here, so a release bumps one file.
163
163
  */
164
- const CLI_VERSION = "0.0.8";
164
+ const CLI_VERSION = "0.0.9";
165
165
  //#endregion
166
166
  //#region src/prompt.ts
167
167
  /**
@@ -284,6 +284,34 @@ async function resolveWorkspace(creds) {
284
284
  };
285
285
  }
286
286
  /**
287
+ * What a refused rename means for whoever asked for it.
288
+ *
289
+ * Shared by the two places that offer a rename, so they cannot drift into
290
+ * telling a user different things about the same reason — and `retryable` is
291
+ * what separates a name worth retyping from one that no name would fix.
292
+ */
293
+ function renameAdvice(reason) {
294
+ switch (reason) {
295
+ case "taken":
296
+ case "reserved": return {
297
+ text: "Pick a different name.",
298
+ retryable: true
299
+ };
300
+ case "malformed": return {
301
+ text: "Use lowercase letters, numbers and single hyphens.",
302
+ retryable: true
303
+ };
304
+ case "locked": return {
305
+ text: "A deploy landed while this was running, which fixed the address for good.",
306
+ retryable: false
307
+ };
308
+ default: return {
309
+ text: "",
310
+ retryable: false
311
+ };
312
+ }
313
+ }
314
+ /**
287
315
  * Ask for the workspace's public address to become `slug`.
288
316
  *
289
317
  * The slug is sent exactly as typed. Sanitizing it the way an App slug is
@@ -301,9 +329,8 @@ async function renameTenantSlug(creds, slug) {
301
329
  });
302
330
  if (!res.ok) throw await asGatewayError(res, `Rename failed (HTTP ${res.status}).`);
303
331
  const result = await res.json();
304
- const stored = readCredentials();
305
- if (stored) writeCredentials({
306
- ...stored,
332
+ writeCredentials({
333
+ ...readCredentials() ?? creds,
307
334
  tenant_slug: result.tenant_slug
308
335
  });
309
336
  return result;
@@ -1410,18 +1437,25 @@ async function promptForDeclaredEnv(session, declared) {
1410
1437
  return answers;
1411
1438
  }
1412
1439
  /**
1413
- * Whether the stored session is one the platform still accepts.
1440
+ * The stored credentials, if the platform still accepts them, with whatever it
1441
+ * said about the Tenant while answering.
1414
1442
  *
1415
- * Only a refusal counts as an answer. An unreachable gateway says nothing about
1416
- * whether the credentials are good, and treating it as a dead session would send
1417
- * a user who is merely offline through a login they do not need.
1443
+ * Only a refusal counts as an answer — that is the null. An unreachable gateway
1444
+ * says nothing about whether the credentials are good, and treating it as a dead
1445
+ * session would send a user who is merely offline through a login they do not
1446
+ * need, so it comes back as a session with nothing known about the Tenant.
1418
1447
  */
1419
- async function sessionIsLive(creds) {
1448
+ async function probeSession(creds) {
1420
1449
  try {
1421
- await fetchTenantDetails(creds);
1422
- return true;
1450
+ return {
1451
+ creds,
1452
+ tenant: await fetchTenantDetails(creds)
1453
+ };
1423
1454
  } catch (err) {
1424
- return !(err instanceof GatewayError && (err.status === 401 || err.status === 403));
1455
+ return err instanceof GatewayError && (err.status === 401 || err.status === 403) ? null : {
1456
+ creds,
1457
+ tenant: null
1458
+ };
1425
1459
  }
1426
1460
  }
1427
1461
  /**
@@ -1438,7 +1472,10 @@ async function sessionIsLive(creds) {
1438
1472
  */
1439
1473
  async function ensureSession(options) {
1440
1474
  const stored = readCredentials();
1441
- if (stored && await sessionIsLive(stored)) return stored;
1475
+ if (stored) {
1476
+ const probed = await probeSession(stored);
1477
+ if (probed) return probed;
1478
+ }
1442
1479
  console.log(stored ? "\n[wawesome] Your session has expired." : "\n[wawesome] You're not logged in yet — that's what a deploy needs.");
1443
1480
  if (!isInteractive()) {
1444
1481
  console.log("[wawesome] Run 'wawesome login', then 'wawesome deploy'.");
@@ -1464,7 +1501,68 @@ async function ensureSession(options) {
1464
1501
  console.error(`[wawesome] Login failed: ${errorText(err)}`);
1465
1502
  return null;
1466
1503
  }
1467
- return readCredentials();
1504
+ const fresh = readCredentials();
1505
+ return fresh ? probeSession(fresh) : null;
1506
+ }
1507
+ /** Bound on the rename loop, so a name the gateway keeps refusing ends the offer. */
1508
+ const MAX_RENAME_ATTEMPTS = 3;
1509
+ /** Show the address the Function will answer on, once it is deployed. */
1510
+ function announceUrl(creds, slug, appSlug, functionName) {
1511
+ console.log("\n[wawesome] Your Function will answer on:\n");
1512
+ console.log(` \x1b[36m${publicInvokeUrl(creds.gateway_url, slug, appSlug, functionName)}\x1b[0m\n`);
1513
+ }
1514
+ /**
1515
+ * Show the URL, and offer to fix the workspace address while it can still be fixed.
1516
+ *
1517
+ * This is the last moment that address is free: it locks the first time a
1518
+ * version is promoted, which is what the deploy at the end of this flow does.
1519
+ * Asking here rather than at signup is the whole reason naming was deferred —
1520
+ * it is client-facing branding, and nobody chooses it well before seeing the
1521
+ * product work. The URL comes first so the offer is about something concrete.
1522
+ *
1523
+ * The lock is read from the gateway rather than guessed, so a user who cannot
1524
+ * rename is told why instead of being walked into a rejection. Nothing here can
1525
+ * fail the flow: the project is already on disk and the deploy still works under
1526
+ * the address the workspace has.
1527
+ */
1528
+ async function offerWorkspaceAddress(session, creds, tenant, appSlug, functionName) {
1529
+ const current = tenant?.tenant_slug ?? creds.tenant_slug;
1530
+ if (!current) return;
1531
+ announceUrl(creds, current, appSlug, functionName);
1532
+ if (!tenant) {
1533
+ console.log(` '${current}' is your workspace address. Whether it can still be changed`);
1534
+ console.log(" is a question for the gateway, which did not answer.\n");
1535
+ return;
1536
+ }
1537
+ if (tenant.slug_locked) {
1538
+ console.log(` 🔒 '${current}' is fixed — a Function version has been promoted, and live URLs already carry it.\n`);
1539
+ return;
1540
+ }
1541
+ console.log(` '${current}' is your workspace address — the part of that URL that is yours`);
1542
+ console.log(" rather than this project's, and this is the moment to change it: it locks");
1543
+ console.log(" for good the first time you deploy, because live URLs carry it.");
1544
+ console.log(" Press enter to keep it.\n");
1545
+ let refusal;
1546
+ for (let attempt = 0; attempt < MAX_RENAME_ATTEMPTS; attempt++) {
1547
+ const answer = await session.ask(" Workspace address?", current);
1548
+ if (answer === current) {
1549
+ console.log(`[wawesome] Keeping '${current}'.`);
1550
+ return;
1551
+ }
1552
+ try {
1553
+ const renamed = await renameTenantSlug(creds, answer);
1554
+ console.log(`\n[wawesome] ✅ Workspace renamed to '${renamed.tenant_slug}'.`);
1555
+ announceUrl(creds, renamed.tenant_slug, appSlug, functionName);
1556
+ return;
1557
+ } catch (err) {
1558
+ console.log(`[wawesome] ${errorText(err)}`);
1559
+ refusal = err instanceof GatewayError ? err.reason : void 0;
1560
+ const { text, retryable } = renameAdvice(refusal);
1561
+ if (text) console.log(`[wawesome] ${text}`);
1562
+ if (!retryable) break;
1563
+ }
1564
+ }
1565
+ console.log(refusal === "locked" ? `[wawesome] Keeping '${current}'.` : `[wawesome] Keeping '${current}'. Change it later with: wawesome workspace rename <name>`);
1468
1566
  }
1469
1567
  /**
1470
1568
  * Satisfy the template's declarations against the platform.
@@ -1538,7 +1636,7 @@ async function initFromTemplate(templateName, options) {
1538
1636
  let functionName;
1539
1637
  let appSlug;
1540
1638
  let manifest;
1541
- let creds;
1639
+ let tenantSession;
1542
1640
  let answers = [];
1543
1641
  try {
1544
1642
  let files;
@@ -1553,7 +1651,7 @@ async function initFromTemplate(templateName, options) {
1553
1651
  }
1554
1652
  const collisions = collidingPaths(cwd, files);
1555
1653
  if (collisions.length > 0) fail(`${collisions.join(", ")} already exist${collisions.length === 1 ? "s" : ""} here.`, "Run this in an empty directory, or move those files aside first. Nothing was written.");
1556
- creds = await ensureSession(options);
1654
+ tenantSession = await ensureSession(options);
1557
1655
  const session = openPromptSession();
1558
1656
  try {
1559
1657
  functionName = await promptForSlug(session, "Function name", readFunctionConfig(staging)?.function || dirName);
@@ -1572,7 +1670,8 @@ async function initFromTemplate(templateName, options) {
1572
1670
  }
1573
1671
  console.log(`\n[wawesome] ✅ Scaffolded ${files.length} files from '${template.name}'.`);
1574
1672
  if (repinned) console.log(`[wawesome] Using wawesome ${repinned.to} — the template pinned ${repinned.from}.`);
1575
- answers = creds ? await promptForDeclaredEnv(session, manifest.env) : [];
1673
+ if (tenantSession) await offerWorkspaceAddress(session, tenantSession.creds, tenantSession.tenant, appSlug, functionName);
1674
+ answers = tenantSession ? await promptForDeclaredEnv(session, manifest.env) : [];
1576
1675
  } finally {
1577
1676
  session.close();
1578
1677
  }
@@ -1580,7 +1679,7 @@ async function initFromTemplate(templateName, options) {
1580
1679
  sweepStaging();
1581
1680
  process.off("exit", sweepStaging);
1582
1681
  }
1583
- if (!creds) {
1682
+ if (!tenantSession) {
1584
1683
  const installed = options.install === false ? null : installDependencies(cwd, { verbose: options.verbose });
1585
1684
  console.log("\n[wawesome] 🎉 Project ready. Next steps:");
1586
1685
  [
@@ -1591,7 +1690,7 @@ async function initFromTemplate(templateName, options) {
1591
1690
  console.log("");
1592
1691
  return;
1593
1692
  }
1594
- await wireUp(creds, appSlug, manifest, answers);
1693
+ await wireUp(tenantSession.creds, appSlug, manifest, answers);
1595
1694
  if (options.install !== false) installDependencies(cwd, { verbose: options.verbose });
1596
1695
  const result = await deploy(void 0, {
1597
1696
  out: "dist/index.js",
@@ -2408,20 +2507,8 @@ async function renameWorkspace(slug, options) {
2408
2507
  } catch (err) {
2409
2508
  if (err instanceof GatewayError) {
2410
2509
  console.error(`\n[wawesome] \x1b[31m${err.message}\x1b[0m`);
2411
- switch (err.reason) {
2412
- case "taken":
2413
- console.error("[wawesome] Pick a different name.");
2414
- break;
2415
- case "reserved":
2416
- console.error("[wawesome] Pick a different name.");
2417
- break;
2418
- case "malformed":
2419
- console.error("[wawesome] Use lowercase letters, numbers and single hyphens.");
2420
- break;
2421
- case "locked":
2422
- console.error("[wawesome] A deploy landed while this was running, which fixed the address for good.");
2423
- break;
2424
- }
2510
+ const { text } = renameAdvice(err.reason);
2511
+ if (text) console.error(`[wawesome] ${text}`);
2425
2512
  console.error("");
2426
2513
  process.exit(1);
2427
2514
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {