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.
- package/README.md +12 -2
- package/dist/index.mjs +213 -33
- 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
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1388
|
-
|
|
1389
|
-
appSlug = await promptForSlug(session, "App slug", dirName);
|
|
1556
|
+
creds = await ensureSession(options);
|
|
1557
|
+
const session = openPromptSession();
|
|
1390
1558
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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":
|
|
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
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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(
|
|
2485
|
+
cli.version(CLI_VERSION);
|
|
2306
2486
|
cli.parse();
|
|
2307
2487
|
//#endregion
|
|
2308
2488
|
export {};
|