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.
- package/README.md +7 -0
- package/dist/index.mjs +120 -33
- 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.
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
*
|
|
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
|
|
1416
|
-
* whether the credentials are good, and treating it as a dead
|
|
1417
|
-
* a user who is merely offline through a login they do not
|
|
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
|
|
1448
|
+
async function probeSession(creds) {
|
|
1420
1449
|
try {
|
|
1421
|
-
|
|
1422
|
-
|
|
1450
|
+
return {
|
|
1451
|
+
creds,
|
|
1452
|
+
tenant: await fetchTenantDetails(creds)
|
|
1453
|
+
};
|
|
1423
1454
|
} catch (err) {
|
|
1424
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
2412
|
-
|
|
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;
|