wensity 0.1.3 → 0.1.5
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/dist/index.js +312 -72
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname as dirname5, join as join6 } from "path";
|
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
var CLI_NAME = "wensity";
|
|
9
|
-
var CLI_VERSION = "0.1.
|
|
9
|
+
var CLI_VERSION = "0.1.5";
|
|
10
10
|
var CONFIG_FILE_NAME = "wensity.json";
|
|
11
11
|
var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
|
|
12
12
|
var DEFAULT_REGISTRY_PATH = "/api/registry";
|
|
@@ -396,6 +396,177 @@ function requiredHome(env) {
|
|
|
396
396
|
throw new CliError("HOME is not set, so the CLI cannot locate ~/.config/wensity.");
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
// src/output.ts
|
|
400
|
+
import boxen from "boxen";
|
|
401
|
+
import Table from "cli-table3";
|
|
402
|
+
import ora from "ora";
|
|
403
|
+
import pc from "picocolors";
|
|
404
|
+
var BRAND_RED = "#cd1c18";
|
|
405
|
+
var PRICING_BLUE = "#2563eb";
|
|
406
|
+
var LOGO = [
|
|
407
|
+
"\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
408
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D",
|
|
409
|
+
"\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ",
|
|
410
|
+
"\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ",
|
|
411
|
+
"\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
412
|
+
" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
|
|
413
|
+
];
|
|
414
|
+
function print(message = "") {
|
|
415
|
+
process.stdout.write(`${message}
|
|
416
|
+
`);
|
|
417
|
+
}
|
|
418
|
+
function warn(message) {
|
|
419
|
+
process.stderr.write(`${pc.yellow("Warning:")} ${message}
|
|
420
|
+
`);
|
|
421
|
+
}
|
|
422
|
+
function printJson(value) {
|
|
423
|
+
print(JSON.stringify(value, null, 2));
|
|
424
|
+
}
|
|
425
|
+
function formatList(items) {
|
|
426
|
+
return items.map((item) => ` ${pc.dim("\u2022")} ${item}`).join("\n");
|
|
427
|
+
}
|
|
428
|
+
function brandRed(message) {
|
|
429
|
+
return color(message, BRAND_RED);
|
|
430
|
+
}
|
|
431
|
+
function pricingBlue(message) {
|
|
432
|
+
return color(message, PRICING_BLUE);
|
|
433
|
+
}
|
|
434
|
+
function brandBanner(subtitle) {
|
|
435
|
+
const lines = [
|
|
436
|
+
"",
|
|
437
|
+
...LOGO.map((line) => brandRed(centerLine(line))),
|
|
438
|
+
pc.bold(centerLine("Wensity CLI"))
|
|
439
|
+
];
|
|
440
|
+
if (subtitle) lines.push(pc.dim(centerLine(subtitle)));
|
|
441
|
+
return lines.join("\n");
|
|
442
|
+
}
|
|
443
|
+
function printBrand(subtitle) {
|
|
444
|
+
print(brandBanner(subtitle));
|
|
445
|
+
}
|
|
446
|
+
function printTitle(title, subtitle) {
|
|
447
|
+
print(`${brandRed("\u25C6")} ${pc.bold(title)}${subtitle ? ` ${pc.dim(subtitle)}` : ""}`);
|
|
448
|
+
}
|
|
449
|
+
function success(message) {
|
|
450
|
+
return `${pc.green("\u2713")} ${message}`;
|
|
451
|
+
}
|
|
452
|
+
function failure(message) {
|
|
453
|
+
return `${brandRed("\u2715")} ${message}`;
|
|
454
|
+
}
|
|
455
|
+
function action(message) {
|
|
456
|
+
return `${brandRed("\u25C6")} ${message}`;
|
|
457
|
+
}
|
|
458
|
+
function badge(label, tone = "muted") {
|
|
459
|
+
const text = ` ${label.toUpperCase()} `;
|
|
460
|
+
if (tone === "pro") return bgBrandRed(pc.white(pc.bold(text)));
|
|
461
|
+
if (tone === "free") return pc.bgGreen(pc.black(pc.bold(text)));
|
|
462
|
+
return pc.inverse(text);
|
|
463
|
+
}
|
|
464
|
+
function panel(title, body) {
|
|
465
|
+
const content = Array.isArray(body) ? body.join("\n") : body;
|
|
466
|
+
return boxen(content, {
|
|
467
|
+
title: brandRed(pc.bold(title)),
|
|
468
|
+
titleAlignment: "left",
|
|
469
|
+
borderColor: "red",
|
|
470
|
+
borderStyle: "round",
|
|
471
|
+
padding: 1,
|
|
472
|
+
margin: { top: 1, bottom: 0 }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function nextSteps(steps) {
|
|
476
|
+
return panel(
|
|
477
|
+
"Next steps",
|
|
478
|
+
steps.map((step) => `${brandRed("\u203A")} ${pc.bold(step)}`)
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
function table(head, rows) {
|
|
482
|
+
const output = new Table({
|
|
483
|
+
head: head.map((item) => brandRed(pc.bold(item))),
|
|
484
|
+
chars: {
|
|
485
|
+
top: "\u2500",
|
|
486
|
+
"top-mid": "\u252C",
|
|
487
|
+
"top-left": "\u256D",
|
|
488
|
+
"top-right": "\u256E",
|
|
489
|
+
bottom: "\u2500",
|
|
490
|
+
"bottom-mid": "\u2534",
|
|
491
|
+
"bottom-left": "\u2570",
|
|
492
|
+
"bottom-right": "\u256F",
|
|
493
|
+
left: "\u2502",
|
|
494
|
+
"left-mid": "\u251C",
|
|
495
|
+
mid: "\u2500",
|
|
496
|
+
"mid-mid": "\u253C",
|
|
497
|
+
right: "\u2502",
|
|
498
|
+
"right-mid": "\u2524",
|
|
499
|
+
middle: "\u2502"
|
|
500
|
+
},
|
|
501
|
+
style: {
|
|
502
|
+
border: [],
|
|
503
|
+
head: []
|
|
504
|
+
},
|
|
505
|
+
wordWrap: true
|
|
506
|
+
});
|
|
507
|
+
output.push(...rows);
|
|
508
|
+
return output.toString();
|
|
509
|
+
}
|
|
510
|
+
function startSpinner(text) {
|
|
511
|
+
return ora({
|
|
512
|
+
text,
|
|
513
|
+
color: "red",
|
|
514
|
+
spinner: "dots",
|
|
515
|
+
isEnabled: process.stderr.isTTY && process.env.CI !== "true"
|
|
516
|
+
}).start();
|
|
517
|
+
}
|
|
518
|
+
async function withSpinner(text, task, successText = text) {
|
|
519
|
+
const spinner = startSpinner(text);
|
|
520
|
+
try {
|
|
521
|
+
const result = await task();
|
|
522
|
+
spinner.succeed(successText);
|
|
523
|
+
return result;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
spinner.fail(text);
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function formatCliError(message) {
|
|
530
|
+
return `${failure(message)}
|
|
531
|
+
|
|
532
|
+
${pc.dim("Need help?")} ${pricingBlue("https://ui.wensity.com/docs")}`;
|
|
533
|
+
}
|
|
534
|
+
function formatProRequiredError(resourceName, resourceType) {
|
|
535
|
+
return [
|
|
536
|
+
`${resourceName} is a Pro ${resourceType}.`,
|
|
537
|
+
brandRed("Create an API token from the Wensity dashboard, then run:"),
|
|
538
|
+
` ${brandRed("npx wensity login --token <token>")}`,
|
|
539
|
+
`Pricing: ${pricingBlue("https://ui.wensity.com/pricing")}`
|
|
540
|
+
].join("\n");
|
|
541
|
+
}
|
|
542
|
+
function color(message, hex) {
|
|
543
|
+
if (!shouldColor()) return message;
|
|
544
|
+
const [red, green, blue] = hexToRgb(hex);
|
|
545
|
+
return `\x1B[38;2;${red};${green};${blue}m${message}\x1B[39m`;
|
|
546
|
+
}
|
|
547
|
+
function bgBrandRed(message) {
|
|
548
|
+
if (!shouldColor()) return message;
|
|
549
|
+
const [red, green, blue] = hexToRgb(BRAND_RED);
|
|
550
|
+
return `\x1B[48;2;${red};${green};${blue}m${message}\x1B[49m`;
|
|
551
|
+
}
|
|
552
|
+
function shouldColor() {
|
|
553
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true;
|
|
554
|
+
return process.env.NO_COLOR === void 0 && process.env.FORCE_COLOR !== "0";
|
|
555
|
+
}
|
|
556
|
+
function hexToRgb(hex) {
|
|
557
|
+
const value = hex.replace("#", "");
|
|
558
|
+
return [
|
|
559
|
+
Number.parseInt(value.slice(0, 2), 16),
|
|
560
|
+
Number.parseInt(value.slice(2, 4), 16),
|
|
561
|
+
Number.parseInt(value.slice(4, 6), 16)
|
|
562
|
+
];
|
|
563
|
+
}
|
|
564
|
+
function centerLine(line) {
|
|
565
|
+
const columns = Math.min(process.stdout.columns || 80, 100);
|
|
566
|
+
const padding = Math.max(0, Math.floor((columns - line.length) / 2));
|
|
567
|
+
return `${" ".repeat(padding)}${line}`;
|
|
568
|
+
}
|
|
569
|
+
|
|
399
570
|
// src/http.ts
|
|
400
571
|
async function requestJson(url, options = {}) {
|
|
401
572
|
const headers = new Headers(options.headers);
|
|
@@ -438,7 +609,7 @@ function formatErrorMessage(payload) {
|
|
|
438
609
|
if (code === "pro_license_required") {
|
|
439
610
|
const base = message ?? "This component requires Wensity Pro.";
|
|
440
611
|
return `${base}
|
|
441
|
-
Upgrade here: https://ui.wensity.com/pricing`;
|
|
612
|
+
Upgrade here: ${pricingBlue("https://ui.wensity.com/pricing")}`;
|
|
442
613
|
}
|
|
443
614
|
return message;
|
|
444
615
|
}
|
|
@@ -447,28 +618,6 @@ Upgrade here: https://ui.wensity.com/pricing`;
|
|
|
447
618
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
448
619
|
import { spawnSync } from "child_process";
|
|
449
620
|
import { join as join4 } from "path";
|
|
450
|
-
|
|
451
|
-
// src/output.ts
|
|
452
|
-
function print(message = "") {
|
|
453
|
-
process.stdout.write(`${message}
|
|
454
|
-
`);
|
|
455
|
-
}
|
|
456
|
-
function warn(message) {
|
|
457
|
-
process.stderr.write(`Warning: ${message}
|
|
458
|
-
`);
|
|
459
|
-
}
|
|
460
|
-
function info(message) {
|
|
461
|
-
process.stderr.write(`${message}
|
|
462
|
-
`);
|
|
463
|
-
}
|
|
464
|
-
function printJson(value) {
|
|
465
|
-
print(JSON.stringify(value, null, 2));
|
|
466
|
-
}
|
|
467
|
-
function formatList(items) {
|
|
468
|
-
return items.map((item) => ` - ${item}`).join("\n");
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// src/package-manager.ts
|
|
472
621
|
function detectPackageManager(projectRoot) {
|
|
473
622
|
if (existsSync2(join4(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
474
623
|
if (existsSync2(join4(projectRoot, "bun.lockb")) || existsSync2(join4(projectRoot, "bun.lock"))) return "bun";
|
|
@@ -819,7 +968,7 @@ async function run(context) {
|
|
|
819
968
|
return;
|
|
820
969
|
}
|
|
821
970
|
if (hasFlag(parsed, "help") || parsed.positionals.length === 0) {
|
|
822
|
-
print(
|
|
971
|
+
print(formatHelp());
|
|
823
972
|
return;
|
|
824
973
|
}
|
|
825
974
|
const [command, ...positionals] = parsed.positionals;
|
|
@@ -865,9 +1014,10 @@ async function initCommand(context, parsed) {
|
|
|
865
1014
|
const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
|
|
866
1015
|
const dependencies = ["clsx", "tailwind-merge"];
|
|
867
1016
|
if (dryRun) {
|
|
868
|
-
|
|
1017
|
+
printTitle("Project initialization preview");
|
|
1018
|
+
print(action("Would write:"));
|
|
869
1019
|
print(formatList([CONFIG_FILE_NAME, utilityRelativePath]));
|
|
870
|
-
if (install) print(`Would install: ${dependencies.join(" ")}`);
|
|
1020
|
+
if (install) print(action(`Would install: ${dependencies.join(" ")}`));
|
|
871
1021
|
return;
|
|
872
1022
|
}
|
|
873
1023
|
await writeConfigFile(configPath, config, force);
|
|
@@ -877,7 +1027,17 @@ async function initCommand(context, parsed) {
|
|
|
877
1027
|
if (install) {
|
|
878
1028
|
runInstall(context.cwd, missingDependencies(context.cwd, dependencies));
|
|
879
1029
|
}
|
|
880
|
-
|
|
1030
|
+
printTitle("Project initialized");
|
|
1031
|
+
print(panel("Created", [
|
|
1032
|
+
success(CONFIG_FILE_NAME),
|
|
1033
|
+
success(utilityRelativePath),
|
|
1034
|
+
success(`registry: ${apiUrl}`)
|
|
1035
|
+
]));
|
|
1036
|
+
print(nextSteps([
|
|
1037
|
+
"Browse available components: npx wensity list",
|
|
1038
|
+
"Browse premium blocks: npx wensity list --type block",
|
|
1039
|
+
"Install your chosen slug: npx wensity add <component-slug>"
|
|
1040
|
+
]));
|
|
881
1041
|
}
|
|
882
1042
|
async function loginCommand(context, parsed) {
|
|
883
1043
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
|
|
@@ -896,12 +1056,23 @@ async function loginCommand(context, parsed) {
|
|
|
896
1056
|
plan: whoami.plan,
|
|
897
1057
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
898
1058
|
});
|
|
899
|
-
|
|
900
|
-
print(
|
|
1059
|
+
printBrand("API token verified");
|
|
1060
|
+
print(formatAccountPanel(whoami, path));
|
|
1061
|
+
print(nextSteps(whoami.plan?.tier === "pro" ? [
|
|
1062
|
+
"Browse Pro components: npx wensity list",
|
|
1063
|
+
"Browse Pro blocks: npx wensity list --type block",
|
|
1064
|
+
"Install a component: npx wensity add <component-slug>",
|
|
1065
|
+
"Install a block: npx wensity add <block-slug> --type block"
|
|
1066
|
+
] : [
|
|
1067
|
+
"Browse free components: npx wensity list",
|
|
1068
|
+
"Install any Free slug from the table: npx wensity add <slug>",
|
|
1069
|
+
`Upgrade to unlock Pro components and blocks: ${pricingBlue("https://ui.wensity.com/pricing")}`
|
|
1070
|
+
]));
|
|
901
1071
|
}
|
|
902
1072
|
async function logoutCommand(context) {
|
|
903
1073
|
const removed = await deleteCredentials(context.env);
|
|
904
|
-
|
|
1074
|
+
printTitle("Session");
|
|
1075
|
+
print(removed ? success("Logged out of Wensity.") : action("No Wensity credentials were stored."));
|
|
905
1076
|
}
|
|
906
1077
|
async function whoamiCommand(context, parsed) {
|
|
907
1078
|
const credentials = await requireCredentials(context.env);
|
|
@@ -912,10 +1083,8 @@ async function whoamiCommand(context, parsed) {
|
|
|
912
1083
|
printJson({ ...whoami, apiBaseUrl: credentials.apiBaseUrl, credentialsPath: credentialsPath(context.env) });
|
|
913
1084
|
return;
|
|
914
1085
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
print(`Plan: ${whoami.plan.tier}${whoami.plan.status ? ` (${whoami.plan.status})` : ""}`);
|
|
918
|
-
}
|
|
1086
|
+
printBrand("Account");
|
|
1087
|
+
print(formatAccountPanel(whoami, credentialsPath(context.env)));
|
|
919
1088
|
}
|
|
920
1089
|
async function listCommand(context, parsed) {
|
|
921
1090
|
const resourceType = getResourceType(parsed);
|
|
@@ -930,11 +1099,17 @@ async function listCommand(context, parsed) {
|
|
|
930
1099
|
return;
|
|
931
1100
|
}
|
|
932
1101
|
if (response2.blocks.length === 0) {
|
|
933
|
-
|
|
1102
|
+
printBrand("Blocks");
|
|
1103
|
+
print(action("No Wensity blocks found."));
|
|
934
1104
|
return;
|
|
935
1105
|
}
|
|
936
|
-
|
|
1106
|
+
printBrand("Premium blocks");
|
|
937
1107
|
print(formatBlockGroup(response2.blocks));
|
|
1108
|
+
print(nextSteps([
|
|
1109
|
+
brandRed("Create an API token in the dashboard if you have Pro"),
|
|
1110
|
+
brandRed("Save the token: npx wensity login --token <token>"),
|
|
1111
|
+
"Install your chosen block: npx wensity add <block-slug> --type block"
|
|
1112
|
+
]));
|
|
938
1113
|
return;
|
|
939
1114
|
}
|
|
940
1115
|
const response = await requestJson(registry);
|
|
@@ -943,32 +1118,39 @@ async function listCommand(context, parsed) {
|
|
|
943
1118
|
return;
|
|
944
1119
|
}
|
|
945
1120
|
if (response.components.length === 0) {
|
|
946
|
-
|
|
1121
|
+
printBrand("Components");
|
|
1122
|
+
print(action("No registry components found."));
|
|
947
1123
|
return;
|
|
948
1124
|
}
|
|
949
|
-
|
|
1125
|
+
printBrand("Components");
|
|
950
1126
|
print(formatComponentGroup("Free", response.components.filter((component) => component.access !== "pro")));
|
|
951
1127
|
print(formatComponentGroup("Pro", response.components.filter((component) => component.access === "pro")));
|
|
1128
|
+
print(nextSteps([
|
|
1129
|
+
"Choose any Free slug above: npx wensity add <component-slug>",
|
|
1130
|
+
brandRed("For Pro slugs, save an API token: npx wensity login --token <token>"),
|
|
1131
|
+
"Browse layout blocks: npx wensity list --type block"
|
|
1132
|
+
]));
|
|
952
1133
|
}
|
|
953
1134
|
function formatComponentGroup(label, components) {
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1135
|
+
const tone = label.toLowerCase() === "pro" ? "pro" : "free";
|
|
1136
|
+
const rows = components.map((component) => [
|
|
1137
|
+
component.slug,
|
|
1138
|
+
component.category ?? "uncategorized",
|
|
1139
|
+
badge(label, tone)
|
|
1140
|
+
]);
|
|
958
1141
|
return rows.length > 0 ? `
|
|
959
|
-
${label}
|
|
960
|
-
${
|
|
961
|
-
${label}
|
|
962
|
-
|
|
1142
|
+
${badge(label, tone)}
|
|
1143
|
+
${table(["Slug", "Category", "Access"], rows)}` : `
|
|
1144
|
+
${badge(label, tone)}
|
|
1145
|
+
${action("No components")}`;
|
|
963
1146
|
}
|
|
964
1147
|
function formatBlockGroup(blocks) {
|
|
965
|
-
const rows = blocks.map((block) =>
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
${rows.join("\n")}` : "\nPro\n No blocks";
|
|
1148
|
+
const rows = blocks.map((block) => [
|
|
1149
|
+
block.slug,
|
|
1150
|
+
block.category ?? "uncategorized",
|
|
1151
|
+
badge("Pro", "pro")
|
|
1152
|
+
]);
|
|
1153
|
+
return rows.length > 0 ? `${table(["Slug", "Category", "Access"], rows)}` : action("No blocks");
|
|
972
1154
|
}
|
|
973
1155
|
async function addCommand(context, parsed, positionals, updateMode) {
|
|
974
1156
|
const resourceType = getResourceType(parsed);
|
|
@@ -994,54 +1176,86 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
|
|
|
994
1176
|
const plannedFiles = summarizeFiles(loaded.projectRoot, prepared.files);
|
|
995
1177
|
const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
|
|
996
1178
|
if (dryRun) {
|
|
997
|
-
|
|
1179
|
+
printTitle(updateMode ? "Update preview" : "Install preview");
|
|
1180
|
+
print(action("Would write:"));
|
|
998
1181
|
print(formatList(plannedFiles));
|
|
999
|
-
if (install && missing.length > 0) print(`Would install: ${missing.join(" ")}`);
|
|
1182
|
+
if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
|
|
1000
1183
|
return;
|
|
1001
1184
|
}
|
|
1002
1185
|
await writePlannedFiles(prepared.files);
|
|
1003
1186
|
if (install) {
|
|
1004
1187
|
runInstall(loaded.projectRoot, missing);
|
|
1005
1188
|
}
|
|
1006
|
-
|
|
1007
|
-
print(
|
|
1008
|
-
|
|
1189
|
+
printTitle(updateMode ? "Component updated" : "Component installed");
|
|
1190
|
+
print(panel(payload.name, [
|
|
1191
|
+
success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`),
|
|
1192
|
+
...plannedFiles.map((file) => success(file))
|
|
1193
|
+
]));
|
|
1009
1194
|
if (!install && missing.length > 0) {
|
|
1010
1195
|
warn(`Dependencies were not installed: ${missing.join(" ")}`);
|
|
1196
|
+
print(nextSteps([
|
|
1197
|
+
`Install skipped dependencies: npm install ${missing.join(" ")}`,
|
|
1198
|
+
"Run without --no-install next time to let Wensity install them automatically"
|
|
1199
|
+
]));
|
|
1200
|
+
return;
|
|
1011
1201
|
}
|
|
1202
|
+
print(nextSteps([
|
|
1203
|
+
"Import the installed file from your Wensity components folder",
|
|
1204
|
+
resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
|
|
1205
|
+
]));
|
|
1012
1206
|
}
|
|
1013
1207
|
async function fetchComponentPayload(input) {
|
|
1014
|
-
|
|
1015
|
-
|
|
1208
|
+
const metadata = await withSpinner(
|
|
1209
|
+
`Finding ${input.slug}`,
|
|
1210
|
+
() => requestJson(registryMetadataUrl(input.registry, input.slug)),
|
|
1211
|
+
`Found ${input.slug}`
|
|
1212
|
+
);
|
|
1016
1213
|
if (input.credentials) {
|
|
1214
|
+
const credentials = input.credentials;
|
|
1017
1215
|
const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/${input.slug}`;
|
|
1018
|
-
const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(
|
|
1216
|
+
const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(credentials.apiBaseUrl, cliEndpoint);
|
|
1019
1217
|
try {
|
|
1020
|
-
return await
|
|
1218
|
+
return await withSpinner(
|
|
1219
|
+
`Downloading ${metadata.name}`,
|
|
1220
|
+
() => requestJson(endpoint2, { token: credentials.token }),
|
|
1221
|
+
`Downloaded ${metadata.name}`
|
|
1222
|
+
);
|
|
1021
1223
|
} catch (error) {
|
|
1022
1224
|
if (metadata.access === "pro" || !isAuthFailure(error)) throw error;
|
|
1023
1225
|
warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.");
|
|
1024
1226
|
}
|
|
1025
1227
|
}
|
|
1026
1228
|
if (metadata.access === "pro") {
|
|
1027
|
-
throw new CliError(
|
|
1229
|
+
throw new CliError(formatProRequiredError(metadata.name, "component"));
|
|
1028
1230
|
}
|
|
1029
1231
|
const publicSourceEndpoint = metadata.endpoints?.registry ?? registrySlugUrl(input.registry, input.slug);
|
|
1030
1232
|
const endpoint = publicSourceEndpoint.startsWith("http") ? publicSourceEndpoint : joinUrl(registryOrigin(input.registry), publicSourceEndpoint);
|
|
1031
|
-
return
|
|
1233
|
+
return withSpinner(
|
|
1234
|
+
`Downloading ${metadata.name}`,
|
|
1235
|
+
() => requestJson(endpoint),
|
|
1236
|
+
`Downloaded ${metadata.name}`
|
|
1237
|
+
);
|
|
1032
1238
|
}
|
|
1033
1239
|
function isAuthFailure(error) {
|
|
1034
1240
|
return error instanceof CliError && /^(401|403)\b/.test(error.message);
|
|
1035
1241
|
}
|
|
1036
1242
|
async function fetchBlockPayload(input) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1243
|
+
const metadata = await withSpinner(
|
|
1244
|
+
`Finding ${input.slug}`,
|
|
1245
|
+
() => requestJson(registryMetadataUrl(input.registry, input.slug)),
|
|
1246
|
+
`Found ${input.slug}`
|
|
1247
|
+
);
|
|
1039
1248
|
if (!input.credentials) {
|
|
1040
|
-
throw new CliError(
|
|
1249
|
+
throw new CliError(formatProRequiredError(metadata.name, "block"));
|
|
1041
1250
|
}
|
|
1251
|
+
const credentials = input.credentials;
|
|
1042
1252
|
const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/blocks/${input.slug}`;
|
|
1043
|
-
const endpoint = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(
|
|
1044
|
-
return
|
|
1253
|
+
const endpoint = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(credentials.apiBaseUrl, cliEndpoint);
|
|
1254
|
+
return withSpinner(
|
|
1255
|
+
`Downloading ${metadata.name}`,
|
|
1256
|
+
() => requestJson(endpoint, { token: credentials.token }),
|
|
1257
|
+
`Downloaded ${metadata.name}`
|
|
1258
|
+
);
|
|
1045
1259
|
}
|
|
1046
1260
|
function getResourceType(parsed) {
|
|
1047
1261
|
const value = getStringOption(parsed, "type") ?? "component";
|
|
@@ -1056,7 +1270,7 @@ function registryUrlForType(componentRegistry, resourceType, registryOverride) {
|
|
|
1056
1270
|
async function requireCredentials(env) {
|
|
1057
1271
|
const credentials = await readCredentials(env);
|
|
1058
1272
|
if (!credentials) {
|
|
1059
|
-
throw new CliError(
|
|
1273
|
+
throw new CliError(`Not logged in. ${brandRed("Run wensity login with an API token from the Wensity dashboard.")}`);
|
|
1060
1274
|
}
|
|
1061
1275
|
return credentials;
|
|
1062
1276
|
}
|
|
@@ -1072,6 +1286,32 @@ function registryOrigin(registry) {
|
|
|
1072
1286
|
const url = new URL(registry);
|
|
1073
1287
|
return `${url.protocol}//${url.host}`;
|
|
1074
1288
|
}
|
|
1289
|
+
function formatHelp() {
|
|
1290
|
+
return [
|
|
1291
|
+
brandBanner("Own your component source."),
|
|
1292
|
+
"",
|
|
1293
|
+
HELP_TEXT.trimEnd(),
|
|
1294
|
+
"",
|
|
1295
|
+
nextSteps([
|
|
1296
|
+
"npx wensity init",
|
|
1297
|
+
"npx wensity list",
|
|
1298
|
+
"npx wensity add <component-slug>"
|
|
1299
|
+
])
|
|
1300
|
+
].join("\n");
|
|
1301
|
+
}
|
|
1302
|
+
function formatAccountPanel(whoami, savedPath) {
|
|
1303
|
+
const isPro = whoami.plan?.tier === "pro";
|
|
1304
|
+
const lines = [
|
|
1305
|
+
`${badge(isPro ? "Pro" : "Free", isPro ? "pro" : "free")} ${whoami.user.email}`,
|
|
1306
|
+
whoami.plan?.status ? `Status: ${whoami.plan.status}` : "Status: active",
|
|
1307
|
+
`Credentials: ${savedPath}`,
|
|
1308
|
+
"",
|
|
1309
|
+
isPro ? success("Pro components unlocked") : success("Free components unlocked"),
|
|
1310
|
+
isPro ? success("Pro blocks unlocked") : action("Pro blocks locked until upgrade"),
|
|
1311
|
+
isPro ? success("API token verified") : action("Upgrade for premium source access")
|
|
1312
|
+
];
|
|
1313
|
+
return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
|
|
1314
|
+
}
|
|
1075
1315
|
async function writeTextFile(path, content) {
|
|
1076
1316
|
await mkdir4(dirname5(path), { recursive: true });
|
|
1077
1317
|
await writeFile4(path, content, "utf8");
|
|
@@ -1084,7 +1324,7 @@ run({
|
|
|
1084
1324
|
env: process.env
|
|
1085
1325
|
}).catch((error) => {
|
|
1086
1326
|
if (error instanceof CliError) {
|
|
1087
|
-
process.stderr.write(`${error.message}
|
|
1327
|
+
process.stderr.write(`${formatCliError(error.message)}
|
|
1088
1328
|
`);
|
|
1089
1329
|
process.exitCode = error.exitCode;
|
|
1090
1330
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wensity",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Wensity component registry CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,5 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"boxen": "^8.0.1",
|
|
34
|
+
"cli-table3": "^0.6.5",
|
|
35
|
+
"ora": "^9.4.0",
|
|
36
|
+
"picocolors": "^1.1.1"
|
|
31
37
|
}
|
|
32
38
|
}
|