@flow-like/widget-bundler 0.1.1

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/src/cli.ts ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env bun
2
+ import { statSync } from "node:fs";
3
+ import { parseArgs } from "node:util";
4
+ import { addWidget } from "./add";
5
+ import { DEFAULT_HARNESS_PORT, startDevServer } from "./dev/server";
6
+ import { pack } from "./pack";
7
+ import { validateBundle, validateProject } from "./validate-cmd";
8
+
9
+ const USAGE = `flow-like-widgets — Flow-Like widget bundler
10
+
11
+ Usage:
12
+ flow-like-widgets pack --project <dir> --out <file> [--serving-prefix <url>] [--created-at <iso>]
13
+ flow-like-widgets dev [--project <dir>] [--port <n>]
14
+ flow-like-widgets validate <project-dir | file.flwb>
15
+ flow-like-widgets add <widget-id> [--group <dir>]
16
+
17
+ Commands:
18
+ pack Build widgets.flwb from a package project (framework groups must be built first)
19
+ dev Start every framework group's dev server + the mock-host harness (default port ${DEFAULT_HARNESS_PORT})
20
+ validate Validate a project's widget contracts, or a built .flwb bundle
21
+ add Scaffold src/widgets/<widget-id>/ inside a framework group
22
+ `;
23
+
24
+ function fail(message: string): never {
25
+ console.error(`error: ${message}`);
26
+ process.exit(1);
27
+ }
28
+
29
+ const CONNECT_FLAG_REMOVED =
30
+ 'The --connect flag was removed: declare network sources per widget in widget.config.ts "csp"';
31
+
32
+ async function runPack(args: string[]): Promise<void> {
33
+ if (args.some((arg) => arg === "--connect" || arg.startsWith("--connect="))) {
34
+ fail(CONNECT_FLAG_REMOVED);
35
+ }
36
+ const { values } = parseArgs({
37
+ args,
38
+ options: {
39
+ project: { type: "string", default: "." },
40
+ out: { type: "string" },
41
+ "serving-prefix": { type: "string" },
42
+ "created-at": { type: "string" },
43
+ },
44
+ });
45
+ const result = await pack(values.project, {
46
+ out: values.out,
47
+ servingPrefix: values["serving-prefix"] ?? null,
48
+ createdAt: values["created-at"],
49
+ });
50
+ for (const warning of result.warnings) {
51
+ console.warn(`warning: ${warning}`);
52
+ }
53
+ }
54
+
55
+ async function runDev(args: string[]): Promise<void> {
56
+ const { values } = parseArgs({
57
+ args,
58
+ options: {
59
+ project: { type: "string", default: "." },
60
+ port: { type: "string" },
61
+ },
62
+ });
63
+ let port: number | undefined;
64
+ if (values.port !== undefined) {
65
+ port = Number(values.port);
66
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
67
+ fail(`invalid --port '${values.port}' (expected 1-65535)`);
68
+ }
69
+ }
70
+ const handle = await startDevServer(values.project, { port });
71
+ console.log(`\n[harness] mock host ready: ${handle.url}\n`);
72
+ }
73
+
74
+ function runValidate(args: string[]): void {
75
+ const { positionals } = parseArgs({
76
+ args,
77
+ allowPositionals: true,
78
+ options: {},
79
+ });
80
+ const target = positionals[0] ?? ".";
81
+ const isBundle = (() => {
82
+ try {
83
+ return statSync(target).isFile();
84
+ } catch {
85
+ return target.endsWith(".flwb");
86
+ }
87
+ })();
88
+
89
+ if (isBundle) {
90
+ const result = validateBundle(target);
91
+ for (const error of result.errors) console.error(`error: ${error}`);
92
+ if (!result.ok) process.exit(1);
93
+ console.log(
94
+ `ok: ${target} is a valid widget bundle (${result.manifest?.widgets.length ?? 0} widget(s))`,
95
+ );
96
+ return;
97
+ }
98
+
99
+ const result = validateProject(target);
100
+ for (const warning of result.warnings) console.warn(`warning: ${warning}`);
101
+ for (const error of result.errors) console.error(`error: ${error}`);
102
+ if (!result.ok) process.exit(1);
103
+ console.log(
104
+ `ok: ${result.widgets.length} widget contract(s) valid (${result.widgets
105
+ .map((w) => w.id)
106
+ .join(", ")})`,
107
+ );
108
+ }
109
+
110
+ function runAdd(args: string[]): void {
111
+ const { values, positionals } = parseArgs({
112
+ args,
113
+ allowPositionals: true,
114
+ options: { group: { type: "string", default: "." } },
115
+ });
116
+ const widgetId = positionals[0];
117
+ if (!widgetId)
118
+ fail(
119
+ "missing <widget-id> (usage: flow-like-widgets add <widget-id> [--group <dir>])",
120
+ );
121
+ const result = addWidget(values.group, widgetId);
122
+ console.log(`created ${result.widgetDir}`);
123
+ for (const file of result.files) console.log(` ${file}`);
124
+ }
125
+
126
+ async function main(): Promise<void> {
127
+ const [command, ...rest] = process.argv.slice(2);
128
+ try {
129
+ switch (command) {
130
+ case "pack":
131
+ await runPack(rest);
132
+ break;
133
+ case "dev":
134
+ await runDev(rest);
135
+ break;
136
+ case "validate":
137
+ runValidate(rest);
138
+ break;
139
+ case "add":
140
+ runAdd(rest);
141
+ break;
142
+ case "help":
143
+ case "--help":
144
+ case "-h":
145
+ console.log(USAGE);
146
+ break;
147
+ default:
148
+ console.error(USAGE);
149
+ process.exit(1);
150
+ }
151
+ } catch (e) {
152
+ fail(e instanceof Error ? e.message : String(e));
153
+ }
154
+ }
155
+
156
+ await main();