wawesome 0.0.7 → 0.0.8

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 +12 -2
  2. package/dist/index.mjs +213 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -52,8 +52,9 @@ npx wawesome init --template stripe-webhook
52
52
 
53
53
  This goes from nothing to a deployed Function in one command. The template is downloaded over
54
54
  plain HTTP and unpacked — **git is not required**, on any platform — and its files land exactly as
55
- they are published. Only two are touched: the `name` in `package.json`, and the App and Function
56
- names in `wawesome-function.json`.
55
+ they are published. Only the `name` in `package.json`, the App and Function names in
56
+ `wawesome-function.json`, and the `wawesome` dependency are touched — the last so the project you
57
+ get depends on the CLI that scaffolded it rather than the one the template was released against.
57
58
 
58
59
  Templates declare what they need rather than shipping placeholders, so the CLI then asks for each
59
60
  environment variable the template requires — showing where in the provider's own dashboard to find
@@ -61,6 +62,15 @@ the value — stores them (secrets write-only), enables any outbound providers t
61
62
  and deploys. If a value does not exist yet, leave it blank; the CLI tells you the `env set` command
62
63
  to run once it does.
63
64
 
65
+ Dependencies are installed for you (with whatever package manager launched the CLI — `npx`, `pnpm
66
+ dlx`, `bun x`), so the types are there when you open the project and the bundler can resolve the
67
+ template's imports. Pass `--no-install` to do it yourself. A failed install never stops the flow;
68
+ the command to re-run is printed.
69
+
70
+ Being logged in is checked **before** the first question, not at the deploy: an expired session
71
+ offers you a login there and then, and declining still leaves you the project plus the two commands
72
+ that finish it.
73
+
64
74
  Run it in an empty directory: if any file would be overwritten, nothing is written at all and the
65
75
  collision is named.
66
76
 
package/dist/index.mjs CHANGED
@@ -5,8 +5,9 @@ import { build } from "esbuild";
5
5
  import os from "node:os";
6
6
  import http from "node:http";
7
7
  import readline from "node:readline";
8
+ import { confirm, select } from "@inquirer/prompts";
9
+ import { spawnSync } from "node:child_process";
8
10
  import zlib from "node:zlib";
9
- import { select } from "@inquirer/prompts";
10
11
  //#region src/config.ts
11
12
  /**
12
13
  * Hardcoded Supabase project config for the wawesome.io platform.
@@ -150,6 +151,18 @@ async function buildJs(entryInput, options) {
150
151
  }
151
152
  }
152
153
  //#endregion
154
+ //#region src/cli-version.ts
155
+ /**
156
+ * The version of the CLI that is running, read from the package it ships in.
157
+ *
158
+ * Compiled in at bundle time rather than resolved from disk, because `dist/` is
159
+ * what gets published and the manifest next to it is not where a `npx` cache,
160
+ * a global install, and a workspace checkout all agree it will be. Anything
161
+ * that has to name this version — `--version`, the dependency a scaffolded
162
+ * project pins — reads it here, so a release bumps one file.
163
+ */
164
+ const CLI_VERSION = "0.0.8";
165
+ //#endregion
153
166
  //#region src/prompt.ts
154
167
  /**
155
168
  * Open a prompt session on stdin, queueing lines as they arrive.
@@ -806,6 +819,58 @@ async function envCommand(action, key, value, options) {
806
819
  process.exit(1);
807
820
  }
808
821
  //#endregion
822
+ //#region src/install.ts
823
+ /** Package managers whose `install` this understands. */
824
+ const KNOWN_MANAGERS = [
825
+ "npm",
826
+ "pnpm",
827
+ "yarn",
828
+ "bun"
829
+ ];
830
+ /**
831
+ * Which package manager to install with.
832
+ *
833
+ * Taken from the one that launched this process — `pnpm dlx wawesome init`
834
+ * should not leave a `package-lock.json` behind — and npm otherwise, since that
835
+ * is what `npx` is.
836
+ */
837
+ function packageManager(userAgent = process.env.npm_config_user_agent) {
838
+ const name = (userAgent ?? "").split("/")[0];
839
+ return KNOWN_MANAGERS.includes(name) ? name : "npm";
840
+ }
841
+ function installDependencies(dir, options = {}) {
842
+ const manager = packageManager();
843
+ const command = `${manager} install`;
844
+ if (!fs.existsSync(path.join(dir, "package.json"))) return {
845
+ command,
846
+ ok: false
847
+ };
848
+ console.log(`\n[wawesome] Installing dependencies (${command})...`);
849
+ const result = spawnSync(manager, ["install"], {
850
+ cwd: dir,
851
+ stdio: options.verbose ? "inherit" : [
852
+ "ignore",
853
+ "ignore",
854
+ "pipe"
855
+ ],
856
+ shell: process.platform === "win32"
857
+ });
858
+ if (result.status === 0) {
859
+ console.log("[wawesome] ✅ Dependencies installed.");
860
+ return {
861
+ command,
862
+ ok: true
863
+ };
864
+ }
865
+ const stderr = result.stderr?.toString().trim();
866
+ console.log(`[wawesome] ${command} did not finish. Run it yourself when you can.`);
867
+ if (stderr) console.log(`[wawesome] ${stderr.split("\n").slice(-3).join("\n[wawesome] ")}`);
868
+ return {
869
+ command,
870
+ ok: false
871
+ };
872
+ }
873
+ //#endregion
809
874
  //#region src/provision.ts
810
875
  /** The gateway's own words for a failure, or a plain HTTP status if it gave none. */
811
876
  async function rejected(res, fallback) {
@@ -902,15 +967,61 @@ function applyTemplateIdentity(dir, identity) {
902
967
  function: identity.functionName
903
968
  }));
904
969
  }
970
+ /** The package the CLI publishes itself as, and which a template depends on. */
971
+ const CLI_PACKAGE = "wawesome";
972
+ /**
973
+ * Point the template's dependency on the CLI at the CLI that is scaffolding it.
974
+ *
975
+ * A template pins the version it was released against, and below 1.0 a caret
976
+ * range is an exact pin — `^0.0.6` never resolves to `0.0.7`. So a template
977
+ * whose repository has not been touched since installs an older CLI than the
978
+ * one the user just ran, and ships types that predate the platform it is about
979
+ * to deploy to. Rewriting it here keeps the two in step without a template
980
+ * release for every CLI release.
981
+ *
982
+ * Only a declaration that already exists is rewritten: what a template depends
983
+ * on is the template's business, and a template that does not use the CLI does
984
+ * not acquire it here.
985
+ */
986
+ function alignCliDependency(dir, version) {
987
+ const file = path.join(dir, PACKAGE_FILE);
988
+ const pkg = readJson(file);
989
+ const wanted = `^${version}`;
990
+ for (const field of ["dependencies", "devDependencies"]) {
991
+ const deps = pkg[field];
992
+ if (!isRecord(deps)) continue;
993
+ const current = deps[CLI_PACKAGE];
994
+ if (typeof current !== "string" || current === wanted) continue;
995
+ writeJson(file, {
996
+ ...pkg,
997
+ [field]: {
998
+ ...deps,
999
+ [CLI_PACKAGE]: wanted
1000
+ }
1001
+ });
1002
+ return {
1003
+ from: current,
1004
+ to: wanted
1005
+ };
1006
+ }
1007
+ return null;
1008
+ }
1009
+ function isRecord(value) {
1010
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1011
+ }
905
1012
  function patchJson(file, patch) {
1013
+ writeJson(file, patch(readJson(file)));
1014
+ }
1015
+ function readJson(file) {
906
1016
  if (!fs.existsSync(file)) throw new Error(`The template is missing ${path.basename(file)}, which scaffolding must patch.`);
907
- let current;
908
1017
  try {
909
- current = JSON.parse(fs.readFileSync(file, "utf-8"));
1018
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
910
1019
  } catch (err) {
911
1020
  throw new Error(`The template's ${path.basename(file)} is not valid JSON: ${errorText(err)}`);
912
1021
  }
913
- fs.writeFileSync(file, JSON.stringify(patch(current), null, 2) + "\n", "utf-8");
1022
+ }
1023
+ function writeJson(file, content) {
1024
+ fs.writeFileSync(file, JSON.stringify(content, null, 2) + "\n", "utf-8");
914
1025
  }
915
1026
  /**
916
1027
  * Which of the files a scaffold is about to write are already there.
@@ -1299,6 +1410,63 @@ async function promptForDeclaredEnv(session, declared) {
1299
1410
  return answers;
1300
1411
  }
1301
1412
  /**
1413
+ * Whether the stored session is one the platform still accepts.
1414
+ *
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.
1418
+ */
1419
+ async function sessionIsLive(creds) {
1420
+ try {
1421
+ await fetchTenantDetails(creds);
1422
+ return true;
1423
+ } catch (err) {
1424
+ return !(err instanceof GatewayError && (err.status === 401 || err.status === 403));
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Get a working session before the first question is asked.
1429
+ *
1430
+ * The credentials file existing is not the same as being logged in — a JWT
1431
+ * expires — and everything this command does after scaffolding needs a live
1432
+ * session. Checked here, the two answers a user can get are "log in now" and a
1433
+ * project that scaffolds without one. Checked where it used to be, at the first
1434
+ * write, the answer was an error *after* they had typed a webhook secret in.
1435
+ *
1436
+ * Returns null when there is no session to work with, which is a supported way
1437
+ * to finish: the files are still worth having.
1438
+ */
1439
+ async function ensureSession(options) {
1440
+ const stored = readCredentials();
1441
+ if (stored && await sessionIsLive(stored)) return stored;
1442
+ console.log(stored ? "\n[wawesome] Your session has expired." : "\n[wawesome] You're not logged in yet — that's what a deploy needs.");
1443
+ if (!isInteractive()) {
1444
+ console.log("[wawesome] Run 'wawesome login', then 'wawesome deploy'.");
1445
+ return null;
1446
+ }
1447
+ let wantsLogin;
1448
+ try {
1449
+ wantsLogin = await confirm({
1450
+ message: "Log in now?",
1451
+ default: true
1452
+ });
1453
+ } catch {
1454
+ console.log("[wawesome] Cancelled. Nothing was written.");
1455
+ process.exit(130);
1456
+ }
1457
+ if (!wantsLogin) return null;
1458
+ try {
1459
+ await login({
1460
+ api: options.api,
1461
+ verbose: options.verbose
1462
+ });
1463
+ } catch (err) {
1464
+ console.error(`[wawesome] Login failed: ${errorText(err)}`);
1465
+ return null;
1466
+ }
1467
+ return readCredentials();
1468
+ }
1469
+ /**
1302
1470
  * Satisfy the template's declarations against the platform.
1303
1471
  *
1304
1472
  * The manifest declares; nothing here is template-specific. Each declaration
@@ -1367,10 +1535,11 @@ async function initFromTemplate(templateName, options) {
1367
1535
  force: true
1368
1536
  });
1369
1537
  process.once("exit", sweepStaging);
1370
- const session = openPromptSession();
1371
1538
  let functionName;
1372
1539
  let appSlug;
1373
1540
  let manifest;
1541
+ let creds;
1542
+ let answers = [];
1374
1543
  try {
1375
1544
  let files;
1376
1545
  try {
@@ -1384,39 +1553,46 @@ async function initFromTemplate(templateName, options) {
1384
1553
  }
1385
1554
  const collisions = collidingPaths(cwd, files);
1386
1555
  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.");
1387
- functionName = await promptForSlug(session, "Function name", readFunctionConfig(staging)?.function || dirName);
1388
- console.log(" (an App groups the Functions of one project — its slug is part of the public URL)");
1389
- appSlug = await promptForSlug(session, "App slug", dirName);
1556
+ creds = await ensureSession(options);
1557
+ const session = openPromptSession();
1390
1558
  try {
1391
- fs.cpSync(staging, cwd, { recursive: true });
1392
- applyTemplateIdentity(cwd, {
1393
- app: appSlug,
1394
- functionName
1395
- });
1396
- } catch (err) {
1397
- fail(errorText(err), `Some of '${template.name}' may have been written to ${cwd}.`);
1559
+ functionName = await promptForSlug(session, "Function name", readFunctionConfig(staging)?.function || dirName);
1560
+ console.log(" (an App groups the Functions of one project — its slug is part of the public URL)");
1561
+ appSlug = await promptForSlug(session, "App slug", dirName);
1562
+ let repinned = null;
1563
+ try {
1564
+ fs.cpSync(staging, cwd, { recursive: true });
1565
+ applyTemplateIdentity(cwd, {
1566
+ app: appSlug,
1567
+ functionName
1568
+ });
1569
+ repinned = alignCliDependency(cwd, CLI_VERSION);
1570
+ } catch (err) {
1571
+ fail(errorText(err), `Some of '${template.name}' may have been written to ${cwd}.`);
1572
+ }
1573
+ console.log(`\n[wawesome] ✅ Scaffolded ${files.length} files from '${template.name}'.`);
1574
+ if (repinned) console.log(`[wawesome] Using wawesome ${repinned.to} — the template pinned ${repinned.from}.`);
1575
+ answers = creds ? await promptForDeclaredEnv(session, manifest.env) : [];
1576
+ } finally {
1577
+ session.close();
1398
1578
  }
1399
- console.log(`\n[wawesome] ✅ Scaffolded ${files.length} files from '${template.name}'.`);
1400
1579
  } finally {
1401
1580
  sweepStaging();
1402
1581
  process.off("exit", sweepStaging);
1403
1582
  }
1404
- let answers = [];
1405
- let creds;
1406
- try {
1407
- creds = readCredentials();
1408
- if (creds) answers = await promptForDeclaredEnv(session, manifest.env);
1409
- } finally {
1410
- session.close();
1411
- }
1412
1583
  if (!creds) {
1584
+ const installed = options.install === false ? null : installDependencies(cwd, { verbose: options.verbose });
1413
1585
  console.log("\n[wawesome] 🎉 Project ready. Next steps:");
1414
- console.log(" 1. npm install");
1415
- console.log(" 2. wawesome login");
1416
- console.log(" 3. wawesome deploy\n");
1586
+ [
1587
+ ...installed?.ok ? [] : [installed?.command ?? "npm install"],
1588
+ "wawesome login",
1589
+ "wawesome deploy"
1590
+ ].forEach((step, index) => console.log(` ${index + 1}. ${step}`));
1591
+ console.log("");
1417
1592
  return;
1418
1593
  }
1419
1594
  await wireUp(creds, appSlug, manifest, answers);
1595
+ if (options.install !== false) installDependencies(cwd, { verbose: options.verbose });
1420
1596
  const result = await deploy(void 0, {
1421
1597
  out: "dist/index.js",
1422
1598
  verbose: options.verbose
@@ -1491,7 +1667,7 @@ export default {
1491
1667
  typecheck: "tsc --noEmit"
1492
1668
  },
1493
1669
  devDependencies: {
1494
- "wawesome": "^1.0.0",
1670
+ "wawesome": `^${CLI_VERSION}`,
1495
1671
  typescript: "^7.0.0"
1496
1672
  }
1497
1673
  };
@@ -1524,10 +1700,14 @@ export default {
1524
1700
  fs.writeFileSync(gitignorePath, "node_modules\ndist\n", "utf-8");
1525
1701
  console.log("[wawesome] ✅ Created .gitignore");
1526
1702
  }
1703
+ const installed = options.install === false ? null : installDependencies(cwd, { verbose: options.verbose });
1527
1704
  console.log("\n[wawesome] 🎉 Project initialized! Next steps:");
1528
- console.log(" 1. npm install");
1529
- console.log(" 2. wawesome login");
1530
- console.log(" 3. wawesome deploy\n");
1705
+ [
1706
+ ...installed?.ok ? [] : [installed?.command ?? "npm install"],
1707
+ "wawesome login",
1708
+ "wawesome deploy"
1709
+ ].forEach((step, index) => console.log(` ${index + 1}. ${step}`));
1710
+ console.log("");
1531
1711
  }
1532
1712
  //#endregion
1533
1713
  //#region src/version.ts
@@ -2279,7 +2459,7 @@ cli.command("logout", "Clear stored authentication credentials").action(() => lo
2279
2459
  cli.command("whoami", "Show current login session info").action(() => whoami());
2280
2460
  cli.command("workspace [action] [name]", "Show the workspace, or rename its public address").usage("workspace <action> [name]\n\nActions:\n show Show the workspace name, address, and whether it can still change\n rename <name> Change the public address, while nothing live depends on it").example("wawesome workspace").example("wawesome workspace rename northwind").option("-v, --verbose", "Enable verbose debug output").action((action, name, options) => workspaceCommand(action, name, options));
2281
2461
  cli.command("templates [action]", "Browse the template catalog").usage("templates [action]\n\nActions:\n list (ls) Show every available template (default)").example("wawesome templates").example("wawesome templates list").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("-v, --verbose", "Enable verbose debug output").action((action, options) => templatesCommand(action, options));
2282
- cli.command("init", "Scaffold a new function project in the current directory").usage("init [options]\n\nWith --template, the project is fetched from the template catalog, wired up\nfrom what the template declares it needs, and deployed. Run 'wawesome templates'\nto see what is available.").example("wawesome init").example("wawesome init --template stripe-webhook").option("-t, --template <name>", "Scaffold from a catalog template and deploy it").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("-v, --verbose", "Enable verbose debug output").action((options) => init(options));
2462
+ cli.command("init", "Scaffold a new function project in the current directory").usage("init [options]\n\nWith --template, the project is fetched from the template catalog, wired up\nfrom what the template declares it needs, and deployed. Run 'wawesome templates'\nto see what is available.").example("wawesome init").example("wawesome init --template stripe-webhook").option("-t, --template <name>", "Scaffold from a catalog template and deploy it").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--no-install", "Skip installing dependencies after scaffolding").option("-v, --verbose", "Enable verbose debug output").action((options) => init(options));
2283
2463
  cli.command("logs [function-name-or-invocation-id]", "View invocation history, fetch log output, or follow live").usage(`logs [target] [options]
2284
2464
 
2285
2465
  The target argument determines what the command does:
@@ -2302,7 +2482,7 @@ cli.command("logs [function-name-or-invocation-id]", "View invocation history, f
2302
2482
  invocations — new output appears each time the function runs, no need to catch a
2303
2483
  specific invocation. Press Ctrl-C to stop at any time.`).option("-f, --follow", "Stream live output (tail -f style). Follows a running invocation or waits for the next one. Ctrl-C to stop").option("-i, --invocation <id>", "Fetch stdout/stderr log body for a specific invocation ID").option("-a, --app <app>", "App slug override (defaults to wawesome-function.json)").option("-s, --status <status>", "Filter invocations by status (success, error, timeout, running)").option("--success", "Shorthand for --status success").option("--error", "Shorthand for --status error").option("--timeout", "Shorthand for --status timeout").option("--running", "Shorthand for --status running").option("-v, --verbose", "Enable verbose debug output").action((target, options) => logsCommand(target, options));
2304
2484
  cli.help();
2305
- cli.version("1.0.0");
2485
+ cli.version(CLI_VERSION);
2306
2486
  cli.parse();
2307
2487
  //#endregion
2308
2488
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {