@fluid-app/fluid-cli-widget 0.1.0
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 +41 -0
- package/dist/index.d.mts +185 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1405 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
- package/templates/default/.oxlintrc.json +9 -0
- package/templates/default/AGENTS.md +10 -0
- package/templates/default/README.md +38 -0
- package/templates/default/fluid.widget.config.ts +9 -0
- package/templates/default/index.html +12 -0
- package/templates/default/manifest.ts +72 -0
- package/templates/default/package.json.template +37 -0
- package/templates/default/src/builder-preview.tsx +63 -0
- package/templates/default/src/index.ts +109 -0
- package/templates/default/src/preview-entry.tsx +60 -0
- package/templates/default/src/vite-env.d.ts +1 -0
- package/templates/default/src/widget-preview.tsx +17 -0
- package/templates/default/src/widgets/review-carousel/ReviewCarousel.tsx +95 -0
- package/templates/default/styles.css +173 -0
- package/templates/default/tsconfig.json +21 -0
- package/templates/default/vite.config.ts +296 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import type { Plugin, ViteDevServer } from "vite";
|
|
3
|
+
import react from "@vitejs/plugin-react";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react(), fluidWidgetDevRoutes()],
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function fluidWidgetDevRoutes(): Plugin {
|
|
10
|
+
return {
|
|
11
|
+
name: "fluid-widget-dev-routes",
|
|
12
|
+
apply: "serve",
|
|
13
|
+
configureServer(server: ViteDevServer) {
|
|
14
|
+
server.middlewares.use(
|
|
15
|
+
async (req: DevRequest, res: DevResponse, next: () => void) => {
|
|
16
|
+
const pathname = (req.url ?? "").split("?")[0] ?? "";
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
pathname === "/builder-preview" ||
|
|
20
|
+
pathname === "/builder-preview/"
|
|
21
|
+
) {
|
|
22
|
+
await sendTransformedHtml(
|
|
23
|
+
server,
|
|
24
|
+
res,
|
|
25
|
+
"/builder-preview",
|
|
26
|
+
getBuilderPreviewHtml(),
|
|
27
|
+
);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (pathname === "/__preview__" || pathname === "/__preview__/") {
|
|
32
|
+
await sendTransformedHtml(
|
|
33
|
+
server,
|
|
34
|
+
res,
|
|
35
|
+
"/__preview__",
|
|
36
|
+
getPreviewHtml(),
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
pathname === "/__runtime-entry__" ||
|
|
43
|
+
pathname === "/__runtime-entry__/"
|
|
44
|
+
) {
|
|
45
|
+
sendJavaScript(req, res, 'void import("/src/index.ts");\n');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (pathname === "/__manifests__" || pathname === "/__manifests__/") {
|
|
50
|
+
await sendManifests(server, req, res);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
next();
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function sendManifests(
|
|
62
|
+
server: ViteDevServer,
|
|
63
|
+
req: DevRequest,
|
|
64
|
+
res: DevResponse,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
if (handleOptions(req, res)) return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const configModule = (await server.ssrLoadModule(
|
|
70
|
+
"/fluid.widget.config.ts",
|
|
71
|
+
)) as Record<string, unknown>;
|
|
72
|
+
sendJson(
|
|
73
|
+
res,
|
|
74
|
+
sourceWidgetPackagesToManifests(readWidgetPackages(configModule)),
|
|
75
|
+
);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
server.config.logger.error(
|
|
78
|
+
`[fluid] Failed to load widget manifests: ${err}`,
|
|
79
|
+
);
|
|
80
|
+
sendJson(res, { error: String(err) }, 500);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function sendTransformedHtml(
|
|
85
|
+
server: ViteDevServer,
|
|
86
|
+
res: DevResponse,
|
|
87
|
+
route: string,
|
|
88
|
+
html: string,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
try {
|
|
91
|
+
const transformed = await server.transformIndexHtml(route, html);
|
|
92
|
+
setCorsHeaders(res);
|
|
93
|
+
res.setHeader("Content-Type", "text/html");
|
|
94
|
+
res.statusCode = 200;
|
|
95
|
+
res.end(transformed);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
server.config.logger.error(`[fluid] Failed to serve ${route}: ${err}`);
|
|
98
|
+
setCorsHeaders(res);
|
|
99
|
+
res.statusCode = 500;
|
|
100
|
+
res.end(`${route} failed to load`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function sendJavaScript(
|
|
105
|
+
req: DevRequest,
|
|
106
|
+
res: DevResponse,
|
|
107
|
+
source: string,
|
|
108
|
+
): void {
|
|
109
|
+
if (handleOptions(req, res)) return;
|
|
110
|
+
setCorsHeaders(res);
|
|
111
|
+
res.setHeader("Content-Type", "text/javascript");
|
|
112
|
+
res.statusCode = 200;
|
|
113
|
+
res.end(source);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function sendJson(res: DevResponse, body: unknown, statusCode = 200): void {
|
|
117
|
+
setCorsHeaders(res);
|
|
118
|
+
res.setHeader("Content-Type", "application/json");
|
|
119
|
+
res.statusCode = statusCode;
|
|
120
|
+
res.end(JSON.stringify(body, null, 2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function handleOptions(req: DevRequest, res: DevResponse): boolean {
|
|
124
|
+
if (req.method !== "OPTIONS") return false;
|
|
125
|
+
setCorsHeaders(res);
|
|
126
|
+
res.statusCode = 204;
|
|
127
|
+
res.end();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function setCorsHeaders(res: DevResponse): void {
|
|
132
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
133
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
|
|
134
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getBuilderPreviewHtml(): string {
|
|
138
|
+
return `<!doctype html>
|
|
139
|
+
<html lang="en">
|
|
140
|
+
<head>
|
|
141
|
+
<meta charset="UTF-8" />
|
|
142
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
143
|
+
<title>Fluid Widget Builder Preview</title>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
<div id="builder-preview-root"></div>
|
|
147
|
+
<script type="module" src="/src/builder-preview.tsx"></script>
|
|
148
|
+
</body>
|
|
149
|
+
</html>`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getPreviewHtml(): string {
|
|
153
|
+
return `<!doctype html>
|
|
154
|
+
<html lang="en">
|
|
155
|
+
<head>
|
|
156
|
+
<meta charset="UTF-8" />
|
|
157
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
158
|
+
<title>Fluid Widget Iframe Preview</title>
|
|
159
|
+
</head>
|
|
160
|
+
<body>
|
|
161
|
+
<div id="preview-root"></div>
|
|
162
|
+
<script type="module" src="/src/preview-entry.tsx"></script>
|
|
163
|
+
</body>
|
|
164
|
+
</html>`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface DevRequest {
|
|
168
|
+
readonly method?: string;
|
|
169
|
+
readonly url?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface DevResponse {
|
|
173
|
+
statusCode: number;
|
|
174
|
+
setHeader(name: string, value: string): void;
|
|
175
|
+
end(body?: string): void;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface SourceWidgetPackage {
|
|
179
|
+
readonly packageId: string;
|
|
180
|
+
readonly version?: string;
|
|
181
|
+
readonly widgets: readonly SourceWidget[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface SourceWidget {
|
|
185
|
+
readonly name?: unknown;
|
|
186
|
+
readonly displayName?: unknown;
|
|
187
|
+
readonly description?: unknown;
|
|
188
|
+
readonly icon?: unknown;
|
|
189
|
+
readonly category?: unknown;
|
|
190
|
+
readonly propertySchema?: unknown;
|
|
191
|
+
readonly defaultProps?: unknown;
|
|
192
|
+
readonly container?: unknown;
|
|
193
|
+
readonly minSdkVersion?: unknown;
|
|
194
|
+
readonly resizable?: unknown;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function readWidgetPackages(
|
|
198
|
+
configModule: Record<string, unknown>,
|
|
199
|
+
): SourceWidgetPackage[] {
|
|
200
|
+
const packages: SourceWidgetPackage[] = [];
|
|
201
|
+
const seen = new Set<string>();
|
|
202
|
+
const candidates = [
|
|
203
|
+
configModule["widgetPackage"],
|
|
204
|
+
...(Array.isArray(configModule["widgetPackages"])
|
|
205
|
+
? configModule["widgetPackages"]
|
|
206
|
+
: []),
|
|
207
|
+
configModule["default"],
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
for (const candidate of candidates) {
|
|
211
|
+
if (!isSourceWidgetPackage(candidate) || seen.has(candidate.packageId)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
packages.push(candidate);
|
|
216
|
+
seen.add(candidate.packageId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return packages;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function sourceWidgetPackagesToManifests(
|
|
223
|
+
widgetPackages: readonly SourceWidgetPackage[],
|
|
224
|
+
): Record<string, unknown>[] {
|
|
225
|
+
return widgetPackages.flatMap((widgetPackage) =>
|
|
226
|
+
widgetPackage.widgets.flatMap((widget) => {
|
|
227
|
+
const name = readString(widget.name);
|
|
228
|
+
if (!name) return [];
|
|
229
|
+
|
|
230
|
+
const type = `${widgetPackage.packageId}.${name}`;
|
|
231
|
+
const displayName = readString(widget.displayName) ?? name;
|
|
232
|
+
return [
|
|
233
|
+
{
|
|
234
|
+
manifestVersion: 1,
|
|
235
|
+
type,
|
|
236
|
+
displayName,
|
|
237
|
+
description:
|
|
238
|
+
readString(widget.description) ?? `Custom widget ${displayName}`,
|
|
239
|
+
icon: readString(widget.icon) ?? "box",
|
|
240
|
+
category: readString(widget.category) ?? "components",
|
|
241
|
+
propertySchema: {
|
|
242
|
+
...readRecord(widget.propertySchema),
|
|
243
|
+
widgetType: type,
|
|
244
|
+
},
|
|
245
|
+
defaultProps: readRecord(widget.defaultProps),
|
|
246
|
+
container: readString(widget.container) ?? "block",
|
|
247
|
+
minSdkVersion: readString(widget.minSdkVersion) ?? "0.0.0",
|
|
248
|
+
resizable: normalizeResizable(widget.resizable),
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function normalizeResizable(value: unknown): boolean | string {
|
|
256
|
+
if (
|
|
257
|
+
value === true ||
|
|
258
|
+
value === "horizontal" ||
|
|
259
|
+
value === "vertical" ||
|
|
260
|
+
value === "both"
|
|
261
|
+
) {
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (isRecord(value)) {
|
|
266
|
+
const horizontal = value["horizontal"] === true;
|
|
267
|
+
const vertical = value["vertical"] === true;
|
|
268
|
+
if (horizontal && vertical) return "both";
|
|
269
|
+
if (horizontal) return "horizontal";
|
|
270
|
+
if (vertical) return "vertical";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isSourceWidgetPackage(value: unknown): value is SourceWidgetPackage {
|
|
277
|
+
return (
|
|
278
|
+
isRecord(value) &&
|
|
279
|
+
typeof value["packageId"] === "string" &&
|
|
280
|
+
Array.isArray(value["widgets"])
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function readString(value: unknown): string | undefined {
|
|
285
|
+
return typeof value === "string" && value.trim().length > 0
|
|
286
|
+
? value.trim()
|
|
287
|
+
: undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function readRecord(value: unknown): Record<string, unknown> {
|
|
291
|
+
return isRecord(value) ? { ...value } : {};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
295
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
296
|
+
}
|