@kizenapps/cli 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/dist/chromium-extension/manifest.json +17 -0
- package/dist/chromium-extension/rules.json +17 -0
- package/dist/index.js +687 -0
- package/dist/index.js.map +1 -0
- package/dist/viewer/assets/calendarSource.worker-DF0wAwJR.js +24 -0
- package/dist/viewer/assets/expression.worker-_W4VR2xe.js +5 -0
- package/dist/viewer/assets/floatingFrame.worker-DXcDBfqJ.js +23 -0
- package/dist/viewer/assets/generic.worker-CsqlYndL.js +23 -0
- package/dist/viewer/assets/index-CW_UqFf3.css +1 -0
- package/dist/viewer/assets/index-Dx8NdszV.js +637 -0
- package/dist/viewer/assets/recordDetail.worker-Ccm59Np6.js +23 -0
- package/dist/viewer/favicon.png +0 -0
- package/dist/viewer/favicon.svg +4 -0
- package/dist/viewer/icon-192.png +0 -0
- package/dist/viewer/index.html +15 -0
- package/dist/viewer/manifest.json +10 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { program } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/build.ts
|
|
7
|
+
import { createElement } from "react";
|
|
8
|
+
import { render } from "ink";
|
|
9
|
+
|
|
10
|
+
// src/ui/BuildUI.tsx
|
|
11
|
+
import { useEffect, useState } from "react";
|
|
12
|
+
import { Box, Text, useApp } from "ink";
|
|
13
|
+
|
|
14
|
+
// src/lib/runBuild.ts
|
|
15
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
16
|
+
import { join as join2 } from "path";
|
|
17
|
+
import { minifyFiles, packagePlugin, transformDeployablePlugin } from "@kizenapps/packager";
|
|
18
|
+
|
|
19
|
+
// src/lib/readFiles.ts
|
|
20
|
+
import { readdir, readFile } from "fs/promises";
|
|
21
|
+
import { join, relative } from "path";
|
|
22
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".svg"]);
|
|
23
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([".kzn"]);
|
|
24
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".kizenapp", ".github"]);
|
|
25
|
+
async function walk(dir, rootDir) {
|
|
26
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
27
|
+
const paths = [];
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
if (SKIP_DIRS.has(entry.name)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
paths.push(...await walk(join(dir, entry.name), rootDir));
|
|
34
|
+
} else if (entry.isFile()) {
|
|
35
|
+
paths.push(join(dir, entry.name));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return paths;
|
|
39
|
+
}
|
|
40
|
+
async function readLocalFiles(rootDir) {
|
|
41
|
+
const absolutePaths = await walk(rootDir, rootDir);
|
|
42
|
+
return Promise.all(
|
|
43
|
+
absolutePaths.map(async (absPath) => {
|
|
44
|
+
const relPath = relative(rootDir, absPath).split("\\").join("/");
|
|
45
|
+
const dotIndex = relPath.lastIndexOf(".");
|
|
46
|
+
const ext = dotIndex >= 0 ? relPath.slice(dotIndex).toLowerCase() : "";
|
|
47
|
+
if (IMAGE_EXTENSIONS.has(ext)) {
|
|
48
|
+
const buf = await readFile(absPath);
|
|
49
|
+
return { path: relPath, content: "", base64Image: buf.toString("base64") };
|
|
50
|
+
}
|
|
51
|
+
if (BINARY_EXTENSIONS.has(ext)) {
|
|
52
|
+
const buf = await readFile(absPath);
|
|
53
|
+
return { path: relPath, content: "", binaryData: buf };
|
|
54
|
+
}
|
|
55
|
+
const content = await readFile(absPath, "utf-8");
|
|
56
|
+
return { path: relPath, content };
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/lib/runBuild.ts
|
|
62
|
+
var serializePlugin = (plugin) => ({
|
|
63
|
+
...plugin,
|
|
64
|
+
thumbnail: plugin.thumbnail ? Buffer.from(plugin.thumbnail).toString("base64") : null,
|
|
65
|
+
kznFile: plugin.kznFile ? Buffer.from(plugin.kznFile).toString("base64") : null
|
|
66
|
+
});
|
|
67
|
+
async function runBuild(pluginDir, outputDir, onStep) {
|
|
68
|
+
await mkdir(outputDir, { recursive: true });
|
|
69
|
+
onStep?.("reading-files");
|
|
70
|
+
const files = await readLocalFiles(pluginDir);
|
|
71
|
+
onStep?.("minifying");
|
|
72
|
+
const minified = await minifyFiles(files);
|
|
73
|
+
onStep?.("packaging");
|
|
74
|
+
const manifestFile = minified.find((f) => f.path === "kizen.json");
|
|
75
|
+
if (!manifestFile) {
|
|
76
|
+
throw new Error("kizen.json not found in plugin directory.");
|
|
77
|
+
}
|
|
78
|
+
const manifests = JSON.parse(manifestFile.content);
|
|
79
|
+
const packaged = packagePlugin(minified, manifests);
|
|
80
|
+
const deployable = Object.values(packaged).map(transformDeployablePlugin);
|
|
81
|
+
onStep?.("writing-bundle");
|
|
82
|
+
const bundle = deployable.map(serializePlugin);
|
|
83
|
+
await writeFile(join2(outputDir, "bundle.json"), JSON.stringify(bundle, null, 2), "utf-8");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/ui/BuildUI.tsx
|
|
87
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
88
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
89
|
+
var Spinner = () => {
|
|
90
|
+
const [frame, setFrame] = useState(0);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const id = setInterval(() => {
|
|
93
|
+
setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
|
|
94
|
+
}, 80);
|
|
95
|
+
return () => {
|
|
96
|
+
clearInterval(id);
|
|
97
|
+
};
|
|
98
|
+
}, []);
|
|
99
|
+
return /* @__PURE__ */ jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
|
|
100
|
+
};
|
|
101
|
+
var STEPS = [
|
|
102
|
+
"creating-dir",
|
|
103
|
+
"reading-files",
|
|
104
|
+
"minifying",
|
|
105
|
+
"packaging",
|
|
106
|
+
"writing-bundle"
|
|
107
|
+
];
|
|
108
|
+
var STEP_LABELS = {
|
|
109
|
+
"creating-dir": "Creating .kizenapp directory",
|
|
110
|
+
"reading-files": "Reading plugin files",
|
|
111
|
+
minifying: "Minifying scripts",
|
|
112
|
+
packaging: "Packaging plugin",
|
|
113
|
+
"writing-bundle": "Writing bundle.json"
|
|
114
|
+
};
|
|
115
|
+
var BuildUI = ({ outputDir, pluginDir }) => {
|
|
116
|
+
const { exit } = useApp();
|
|
117
|
+
const [step, setStep] = useState("creating-dir");
|
|
118
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
void runBuild(pluginDir, outputDir, setStep).then(() => {
|
|
121
|
+
setStep("done");
|
|
122
|
+
exit();
|
|
123
|
+
}).catch((err) => {
|
|
124
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
125
|
+
setErrorMessage(message);
|
|
126
|
+
setStep("error");
|
|
127
|
+
exit(err instanceof Error ? err : new Error(message));
|
|
128
|
+
});
|
|
129
|
+
}, [outputDir, pluginDir, exit]);
|
|
130
|
+
const currentIndex = STEPS.indexOf(step);
|
|
131
|
+
const isError = step === "error";
|
|
132
|
+
const isDone = step === "done";
|
|
133
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, paddingX: 1, children: [
|
|
134
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
135
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Kizen App Builder" }),
|
|
136
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(22) })
|
|
137
|
+
] }),
|
|
138
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
|
|
139
|
+
STEPS.map((s, i) => {
|
|
140
|
+
const isActive = step === s;
|
|
141
|
+
const isStepDone = isDone || currentIndex > i;
|
|
142
|
+
const isFailed = isError && i === currentIndex;
|
|
143
|
+
return /* @__PURE__ */ jsx(Box, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
144
|
+
"\u2717 ",
|
|
145
|
+
STEP_LABELS[s]
|
|
146
|
+
] }) : isStepDone ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
147
|
+
"\u2713 ",
|
|
148
|
+
STEP_LABELS[s]
|
|
149
|
+
] }) : isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
151
|
+
/* @__PURE__ */ jsx(Text, { children: STEP_LABELS[s] })
|
|
152
|
+
] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
153
|
+
"\xB7 ",
|
|
154
|
+
STEP_LABELS[s]
|
|
155
|
+
] }) }, s);
|
|
156
|
+
}),
|
|
157
|
+
errorMessage !== null && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMessage }) })
|
|
158
|
+
] })
|
|
159
|
+
] });
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/commands/build.ts
|
|
163
|
+
function buildCommand(program2) {
|
|
164
|
+
program2.command("build").description("Bundle the plugin app into .kizenapp directory").action(async () => {
|
|
165
|
+
const outputDir = `${process.cwd()}/.kizenapp`;
|
|
166
|
+
const pluginDir = process.cwd();
|
|
167
|
+
const { waitUntilExit } = render(createElement(BuildUI, { outputDir, pluginDir }));
|
|
168
|
+
await waitUntilExit();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/commands/dev.ts
|
|
173
|
+
import { createElement as createElement2 } from "react";
|
|
174
|
+
import { render as render2 } from "ink";
|
|
175
|
+
|
|
176
|
+
// src/ui/DevUI.tsx
|
|
177
|
+
import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
178
|
+
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
179
|
+
import * as ChromeLauncher from "chrome-launcher";
|
|
180
|
+
import { createReadStream, mkdirSync, watch } from "fs";
|
|
181
|
+
import { access, readFile as readFile2 } from "fs/promises";
|
|
182
|
+
import { createServer } from "http";
|
|
183
|
+
import { dirname, extname, join as join3 } from "path";
|
|
184
|
+
import { fileURLToPath } from "url";
|
|
185
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
186
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
187
|
+
var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
|
|
188
|
+
var LOG_LIMIT = 50;
|
|
189
|
+
var LOG_DISPLAY = 8;
|
|
190
|
+
var MIME_TYPES = {
|
|
191
|
+
".html": "text/html; charset=utf-8",
|
|
192
|
+
".js": "application/javascript; charset=utf-8",
|
|
193
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
194
|
+
".css": "text/css; charset=utf-8",
|
|
195
|
+
".png": "image/png",
|
|
196
|
+
".jpg": "image/jpeg",
|
|
197
|
+
".svg": "image/svg+xml",
|
|
198
|
+
".ico": "image/x-icon",
|
|
199
|
+
".json": "application/json; charset=utf-8",
|
|
200
|
+
".woff": "font/woff",
|
|
201
|
+
".woff2": "font/woff2",
|
|
202
|
+
".ttf": "font/ttf",
|
|
203
|
+
".map": "application/json"
|
|
204
|
+
};
|
|
205
|
+
var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
206
|
+
var Spinner2 = () => {
|
|
207
|
+
const [frame, setFrame] = useState2(0);
|
|
208
|
+
useEffect2(() => {
|
|
209
|
+
const id = setInterval(() => {
|
|
210
|
+
setFrame((prev) => (prev + 1) % SPINNER_FRAMES2.length);
|
|
211
|
+
}, 80);
|
|
212
|
+
return () => {
|
|
213
|
+
clearInterval(id);
|
|
214
|
+
};
|
|
215
|
+
}, []);
|
|
216
|
+
return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
|
|
217
|
+
};
|
|
218
|
+
function getViewerPath() {
|
|
219
|
+
const filename = fileURLToPath(import.meta.url);
|
|
220
|
+
return join3(dirname(filename), "viewer");
|
|
221
|
+
}
|
|
222
|
+
function setupCspBypass(debugPort) {
|
|
223
|
+
void (async () => {
|
|
224
|
+
let wsUrl;
|
|
225
|
+
try {
|
|
226
|
+
const res = await fetch(`http://localhost:${String(debugPort)}/json/version`);
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
wsUrl = data.webSocketDebuggerUrl;
|
|
229
|
+
} catch {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const ws = new WebSocket(wsUrl);
|
|
233
|
+
let msgId = 0;
|
|
234
|
+
const pending = /* @__PURE__ */ new Map();
|
|
235
|
+
const send = (method, params, sessionId) => {
|
|
236
|
+
ws.send(
|
|
237
|
+
JSON.stringify({
|
|
238
|
+
id: ++msgId,
|
|
239
|
+
method,
|
|
240
|
+
...params !== void 0 && { params },
|
|
241
|
+
...sessionId !== void 0 && { sessionId }
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
const sendAndAwait = (method, params, sessionId) => {
|
|
246
|
+
const id = ++msgId;
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
pending.set(id, resolve);
|
|
249
|
+
ws.send(
|
|
250
|
+
JSON.stringify({
|
|
251
|
+
id,
|
|
252
|
+
method,
|
|
253
|
+
...params !== void 0 && { params },
|
|
254
|
+
...sessionId !== void 0 && { sessionId }
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
const CSP_HEADERS = /* @__PURE__ */ new Set([
|
|
260
|
+
"content-security-policy",
|
|
261
|
+
"content-security-policy-report-only",
|
|
262
|
+
"x-frame-options"
|
|
263
|
+
]);
|
|
264
|
+
const KIZEN_DOMAINS = ["kizen.dev", "kizen.com"];
|
|
265
|
+
let primarySession;
|
|
266
|
+
ws.on("open", () => {
|
|
267
|
+
send("Target.setAutoAttach", {
|
|
268
|
+
autoAttach: true,
|
|
269
|
+
waitForDebuggerOnStart: false,
|
|
270
|
+
flatten: true
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
ws.on("message", (data) => {
|
|
274
|
+
const msg = JSON.parse(data.toString());
|
|
275
|
+
if (msg.id !== void 0 && pending.has(msg.id)) {
|
|
276
|
+
pending.get(msg.id)?.(msg.result);
|
|
277
|
+
pending.delete(msg.id);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (msg.method === "Target.attachedToTarget" && msg.params?.sessionId) {
|
|
281
|
+
send(
|
|
282
|
+
"Fetch.enable",
|
|
283
|
+
{ patterns: [{ requestStage: "Response", resourceType: "Document" }] },
|
|
284
|
+
msg.params.sessionId
|
|
285
|
+
);
|
|
286
|
+
send("Network.enable", {}, msg.params.sessionId);
|
|
287
|
+
primarySession ??= msg.params.sessionId;
|
|
288
|
+
}
|
|
289
|
+
if (msg.method === "Fetch.requestPaused" && msg.params?.requestId) {
|
|
290
|
+
const { requestId, responseHeaders, responseStatusCode = 200 } = msg.params;
|
|
291
|
+
const sessionId = msg.sessionId;
|
|
292
|
+
const hasCsp = responseHeaders?.some((h) => CSP_HEADERS.has(h.name.toLowerCase()));
|
|
293
|
+
if (!hasCsp) {
|
|
294
|
+
send("Fetch.continueResponse", { requestId }, sessionId);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
void (async () => {
|
|
298
|
+
try {
|
|
299
|
+
const { body, base64Encoded } = await sendAndAwait(
|
|
300
|
+
"Fetch.getResponseBody",
|
|
301
|
+
{ requestId },
|
|
302
|
+
sessionId
|
|
303
|
+
);
|
|
304
|
+
const filteredHeaders = (responseHeaders ?? []).filter(
|
|
305
|
+
(h) => !CSP_HEADERS.has(h.name.toLowerCase())
|
|
306
|
+
);
|
|
307
|
+
const bodyBase64 = base64Encoded ? body : Buffer.from(body).toString("base64");
|
|
308
|
+
send(
|
|
309
|
+
"Fetch.fulfillRequest",
|
|
310
|
+
{
|
|
311
|
+
requestId,
|
|
312
|
+
responseCode: responseStatusCode,
|
|
313
|
+
responseHeaders: filteredHeaders,
|
|
314
|
+
body: bodyBase64
|
|
315
|
+
},
|
|
316
|
+
sessionId
|
|
317
|
+
);
|
|
318
|
+
} catch {
|
|
319
|
+
send("Fetch.continueResponse", { requestId }, sessionId);
|
|
320
|
+
}
|
|
321
|
+
})();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
ws.on("error", () => {
|
|
325
|
+
});
|
|
326
|
+
setInterval(() => {
|
|
327
|
+
const session = primarySession;
|
|
328
|
+
if (!session) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
void (async () => {
|
|
332
|
+
try {
|
|
333
|
+
const { cookies } = await sendAndAwait("Network.getAllCookies", void 0, session);
|
|
334
|
+
const toFix = cookies.filter(
|
|
335
|
+
(c) => KIZEN_DOMAINS.some((d) => c.domain === d || c.domain.endsWith("." + d)) && c.sameSite !== "None"
|
|
336
|
+
);
|
|
337
|
+
if (toFix.length === 0) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
await sendAndAwait(
|
|
341
|
+
"Network.setCookies",
|
|
342
|
+
{
|
|
343
|
+
cookies: toFix.map(({ expires, ...rest }) => ({
|
|
344
|
+
...rest,
|
|
345
|
+
sameSite: "None",
|
|
346
|
+
secure: true,
|
|
347
|
+
...expires !== -1 && { expires }
|
|
348
|
+
}))
|
|
349
|
+
},
|
|
350
|
+
session
|
|
351
|
+
);
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
})();
|
|
355
|
+
}, 2e3);
|
|
356
|
+
})();
|
|
357
|
+
}
|
|
358
|
+
async function fileExists(filePath) {
|
|
359
|
+
try {
|
|
360
|
+
await access(filePath);
|
|
361
|
+
return true;
|
|
362
|
+
} catch {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function createRequestHandler(viewerPath, createServerLog, createProxyLog) {
|
|
367
|
+
return (req, res) => {
|
|
368
|
+
void (async () => {
|
|
369
|
+
const url = req.url ?? "/";
|
|
370
|
+
createServerLog(`Received request: ${url}`);
|
|
371
|
+
if (url === "/api/bundle") {
|
|
372
|
+
const bundlePath = join3(process.cwd(), ".kizenapp", "bundle.json");
|
|
373
|
+
try {
|
|
374
|
+
const content = await readFile2(bundlePath, "utf-8");
|
|
375
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
376
|
+
res.end(content);
|
|
377
|
+
} catch {
|
|
378
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
379
|
+
res.end("{}");
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (url.startsWith("/api/proxy")) {
|
|
384
|
+
const proxyTarget = req.headers["x-proxy-target"];
|
|
385
|
+
if (typeof proxyTarget !== "string") {
|
|
386
|
+
res.writeHead(400);
|
|
387
|
+
res.end("Missing x-proxy-target header");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const upstreamPath = url.slice("/api/proxy".length) || "/";
|
|
391
|
+
const upstreamUrl = `${proxyTarget}${upstreamPath}`;
|
|
392
|
+
const chunks = [];
|
|
393
|
+
for await (const chunk of req) {
|
|
394
|
+
chunks.push(chunk);
|
|
395
|
+
}
|
|
396
|
+
const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
|
|
397
|
+
const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
|
|
398
|
+
const resolvedBody = body && body.length > 0 ? body : void 0;
|
|
399
|
+
const upstream = await fetch(upstreamUrl, {
|
|
400
|
+
...req.method !== void 0 && { method: req.method },
|
|
401
|
+
headers: forwardHeaders,
|
|
402
|
+
...resolvedBody !== void 0 && { body: resolvedBody }
|
|
403
|
+
});
|
|
404
|
+
createProxyLog(`${req.method ?? "GET"} ${upstreamPath} \u2192 ${String(upstream.status)}`);
|
|
405
|
+
const responseHeaders = Object.fromEntries(upstream.headers);
|
|
406
|
+
delete responseHeaders["content-encoding"];
|
|
407
|
+
delete responseHeaders["content-length"];
|
|
408
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
409
|
+
res.end(Buffer.from(await upstream.arrayBuffer()));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const rawPath = url === "/" ? "/index.html" : url;
|
|
413
|
+
const filePath = join3(viewerPath, rawPath);
|
|
414
|
+
const resolvedPath = await fileExists(filePath) ? filePath : join3(viewerPath, "index.html");
|
|
415
|
+
const ext = extname(resolvedPath);
|
|
416
|
+
const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
417
|
+
res.writeHead(200, { "Content-Type": mimeType });
|
|
418
|
+
createReadStream(resolvedPath).pipe(res);
|
|
419
|
+
})();
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
var DevUI = ({ port, pluginDir, outputDir }) => {
|
|
423
|
+
const [status, setStatus] = useState2("starting");
|
|
424
|
+
const [errorMessage, setErrorMessage] = useState2(null);
|
|
425
|
+
const [serverLogHistory, setServerLogHistory] = useState2([]);
|
|
426
|
+
const [buildLogHistory, setBuildLogHistory] = useState2([]);
|
|
427
|
+
const [proxyLogHistory, setProxyLogHistory] = useState2([]);
|
|
428
|
+
const [buildStatus, setBuildStatus] = useState2("pending");
|
|
429
|
+
const [buildError, setBuildError] = useState2(null);
|
|
430
|
+
const [lastBuilt, setLastBuilt] = useState2(null);
|
|
431
|
+
const [wsClientCount, setWsClientCount] = useState2(0);
|
|
432
|
+
const buildingRef = useRef(false);
|
|
433
|
+
const chromiumLaunchedRef = useRef(false);
|
|
434
|
+
const debounceTimerRef = useRef(null);
|
|
435
|
+
const wsClientsRef = useRef(/* @__PURE__ */ new Set());
|
|
436
|
+
const pendingMessagesRef = useRef([]);
|
|
437
|
+
useInput((input, key) => {
|
|
438
|
+
if (input === "q" || key.ctrl && input === "c") {
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
useEffect2(() => {
|
|
443
|
+
if (status !== "running" || chromiumLaunchedRef.current) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
chromiumLaunchedRef.current = true;
|
|
447
|
+
const chromeDataDir = join3(outputDir, ".chrome");
|
|
448
|
+
mkdirSync(chromeDataDir, { recursive: true });
|
|
449
|
+
void ChromeLauncher.launch({
|
|
450
|
+
startingUrl: "",
|
|
451
|
+
chromeFlags: [`--app=http://localhost:${String(port)}`],
|
|
452
|
+
userDataDir: chromeDataDir,
|
|
453
|
+
logLevel: "silent"
|
|
454
|
+
}).then((chrome) => {
|
|
455
|
+
chrome.process.unref();
|
|
456
|
+
setupCspBypass(chrome.port);
|
|
457
|
+
process.on("exit", () => {
|
|
458
|
+
chrome.process.kill();
|
|
459
|
+
});
|
|
460
|
+
}).catch(() => {
|
|
461
|
+
});
|
|
462
|
+
}, [status, port, outputDir]);
|
|
463
|
+
const createServerLog = useCallback((message) => {
|
|
464
|
+
setServerLogHistory(
|
|
465
|
+
(h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
|
|
466
|
+
);
|
|
467
|
+
}, []);
|
|
468
|
+
const broadcast = useCallback((msg) => {
|
|
469
|
+
const json = JSON.stringify(msg);
|
|
470
|
+
if (wsClientsRef.current.size === 0) {
|
|
471
|
+
pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
for (const client of wsClientsRef.current) {
|
|
475
|
+
if (client.readyState === 1) {
|
|
476
|
+
client.send(json);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}, []);
|
|
480
|
+
const createProxyLog = useCallback(
|
|
481
|
+
(message) => {
|
|
482
|
+
setProxyLogHistory(
|
|
483
|
+
(h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
|
|
484
|
+
);
|
|
485
|
+
broadcast({ type: "proxy-log", message });
|
|
486
|
+
},
|
|
487
|
+
[broadcast]
|
|
488
|
+
);
|
|
489
|
+
const createBuildLog = useCallback(
|
|
490
|
+
(message) => {
|
|
491
|
+
setBuildLogHistory(
|
|
492
|
+
(h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
|
|
493
|
+
);
|
|
494
|
+
broadcast({ type: "log", message });
|
|
495
|
+
},
|
|
496
|
+
[broadcast]
|
|
497
|
+
);
|
|
498
|
+
const triggerBuild = useCallback(() => {
|
|
499
|
+
if (buildingRef.current) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
buildingRef.current = true;
|
|
503
|
+
setBuildStatus("building");
|
|
504
|
+
setBuildError(null);
|
|
505
|
+
createBuildLog("Build started");
|
|
506
|
+
void runBuild(pluginDir, outputDir).then(() => {
|
|
507
|
+
createBuildLog("Build finished");
|
|
508
|
+
setBuildStatus("done");
|
|
509
|
+
setLastBuilt(/* @__PURE__ */ new Date());
|
|
510
|
+
createBuildLog("Notifying viewers to reload");
|
|
511
|
+
for (const client of wsClientsRef.current) {
|
|
512
|
+
if (client.readyState === 1) {
|
|
513
|
+
client.send(JSON.stringify({ type: "rebuild" }));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}).catch((err) => {
|
|
517
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
518
|
+
setBuildError(message);
|
|
519
|
+
setBuildStatus("error");
|
|
520
|
+
}).finally(() => {
|
|
521
|
+
buildingRef.current = false;
|
|
522
|
+
});
|
|
523
|
+
}, [pluginDir, outputDir, createBuildLog]);
|
|
524
|
+
useEffect2(() => {
|
|
525
|
+
triggerBuild();
|
|
526
|
+
const watcher = watch(pluginDir, { recursive: true }, (_, filename) => {
|
|
527
|
+
if (!filename) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const normalized = filename.replace(/\\/g, "/");
|
|
531
|
+
if (SKIP_WATCH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
createBuildLog(`File change detected: ${filename}`);
|
|
535
|
+
if (debounceTimerRef.current !== null) {
|
|
536
|
+
clearTimeout(debounceTimerRef.current);
|
|
537
|
+
}
|
|
538
|
+
debounceTimerRef.current = setTimeout(triggerBuild, 150);
|
|
539
|
+
});
|
|
540
|
+
return () => {
|
|
541
|
+
watcher.close();
|
|
542
|
+
if (debounceTimerRef.current !== null) {
|
|
543
|
+
clearTimeout(debounceTimerRef.current);
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}, [pluginDir, triggerBuild, createBuildLog]);
|
|
547
|
+
useEffect2(() => {
|
|
548
|
+
const viewerPath = getViewerPath();
|
|
549
|
+
void fileExists(join3(viewerPath, "index.html")).then((viewerBuilt) => {
|
|
550
|
+
if (!viewerBuilt) {
|
|
551
|
+
setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
|
|
552
|
+
setStatus("error");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const handler = createRequestHandler(viewerPath, createServerLog, createProxyLog);
|
|
556
|
+
const server = createServer(handler);
|
|
557
|
+
const wss = new WebSocketServer({ server });
|
|
558
|
+
wss.on("connection", (ws) => {
|
|
559
|
+
for (const json of pendingMessagesRef.current) {
|
|
560
|
+
if (ws.readyState === 1) {
|
|
561
|
+
ws.send(json);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
pendingMessagesRef.current = [];
|
|
565
|
+
createServerLog("Viewer connected for live reload");
|
|
566
|
+
wsClientsRef.current.add(ws);
|
|
567
|
+
setWsClientCount(wsClientsRef.current.size);
|
|
568
|
+
ws.on("close", () => {
|
|
569
|
+
wsClientsRef.current.delete(ws);
|
|
570
|
+
setWsClientCount(wsClientsRef.current.size);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
server.listen(port, () => {
|
|
574
|
+
setStatus("running");
|
|
575
|
+
createServerLog(`Server started on port ${String(port)}`);
|
|
576
|
+
});
|
|
577
|
+
server.on("error", (err) => {
|
|
578
|
+
setErrorMessage(err.message);
|
|
579
|
+
setStatus("error");
|
|
580
|
+
});
|
|
581
|
+
return () => {
|
|
582
|
+
wss.close();
|
|
583
|
+
server.close();
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
}, [port, createServerLog, createProxyLog]);
|
|
587
|
+
const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
|
|
588
|
+
const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
|
|
589
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingY: 1, paddingX: 1, children: [
|
|
590
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx2(Box2, { gap: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }) }) }),
|
|
591
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
592
|
+
/* @__PURE__ */ jsxs2(Box2, { gap: 1, marginBottom: 0, children: [
|
|
593
|
+
buildStatus === "pending" && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Build waiting..." }),
|
|
594
|
+
buildStatus === "building" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
595
|
+
/* @__PURE__ */ jsx2(Spinner2, {}),
|
|
596
|
+
/* @__PURE__ */ jsx2(Text2, { children: "Building Plugin Package..." })
|
|
597
|
+
] }),
|
|
598
|
+
buildStatus === "done" && /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
|
|
599
|
+
"\u2713 Built Plugin Package",
|
|
600
|
+
lastBuilt !== null ? ` at ${lastBuilt.toLocaleTimeString()}` : "",
|
|
601
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: elapsedLabel })
|
|
602
|
+
] }),
|
|
603
|
+
buildStatus === "error" && /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
604
|
+
"\u2717 Build error: ",
|
|
605
|
+
buildError ?? "unknown error"
|
|
606
|
+
] })
|
|
607
|
+
] }),
|
|
608
|
+
/* @__PURE__ */ jsx2(
|
|
609
|
+
Box2,
|
|
610
|
+
{
|
|
611
|
+
flexDirection: "column",
|
|
612
|
+
height: LOG_DISPLAY,
|
|
613
|
+
borderStyle: "round",
|
|
614
|
+
borderColor: "gray",
|
|
615
|
+
overflow: "hidden",
|
|
616
|
+
children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: log }, index))
|
|
617
|
+
}
|
|
618
|
+
)
|
|
619
|
+
] }),
|
|
620
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
621
|
+
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
622
|
+
status === "starting" && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Starting server..." }),
|
|
623
|
+
status === "running" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
624
|
+
/* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713 Dev Server running" }),
|
|
625
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "cyan", bold: true, children: [
|
|
626
|
+
"http://localhost:",
|
|
627
|
+
port
|
|
628
|
+
] }),
|
|
629
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
630
|
+
"\u25CF ",
|
|
631
|
+
wsClientCount,
|
|
632
|
+
" viewer",
|
|
633
|
+
wsClientCount !== 1 ? "s" : ""
|
|
634
|
+
] })
|
|
635
|
+
] }),
|
|
636
|
+
status === "error" && /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
637
|
+
"\u2717 ",
|
|
638
|
+
errorMessage ?? "Server error"
|
|
639
|
+
] })
|
|
640
|
+
] }),
|
|
641
|
+
/* @__PURE__ */ jsx2(
|
|
642
|
+
Box2,
|
|
643
|
+
{
|
|
644
|
+
flexDirection: "column",
|
|
645
|
+
height: LOG_DISPLAY,
|
|
646
|
+
borderStyle: "round",
|
|
647
|
+
borderColor: "gray",
|
|
648
|
+
overflow: "hidden",
|
|
649
|
+
children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: log }, index))
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
] }),
|
|
653
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
654
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Proxy" }),
|
|
655
|
+
/* @__PURE__ */ jsx2(
|
|
656
|
+
Box2,
|
|
657
|
+
{
|
|
658
|
+
flexDirection: "column",
|
|
659
|
+
height: LOG_DISPLAY,
|
|
660
|
+
borderStyle: "round",
|
|
661
|
+
borderColor: "gray",
|
|
662
|
+
overflow: "hidden",
|
|
663
|
+
children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " No proxy requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: log }, i))
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "q to quit" }) })
|
|
668
|
+
] });
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// src/commands/dev.ts
|
|
672
|
+
function devCommand(program2) {
|
|
673
|
+
program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3000").action(async (options) => {
|
|
674
|
+
const port = parseInt(options.port, 10);
|
|
675
|
+
const pluginDir = process.cwd();
|
|
676
|
+
const outputDir = `${pluginDir}/.kizenapp`;
|
|
677
|
+
const { waitUntilExit } = render2(createElement2(DevUI, { port, pluginDir, outputDir }), { exitOnCtrlC: false });
|
|
678
|
+
await waitUntilExit();
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/index.ts
|
|
683
|
+
program.name("appbuilder").description("Kizen plugin app builder").version("0.1.0");
|
|
684
|
+
buildCommand(program);
|
|
685
|
+
devCommand(program);
|
|
686
|
+
program.parse();
|
|
687
|
+
//# sourceMappingURL=index.js.map
|