@kizenapps/cli 0.11.0 → 0.13.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/index.js +1644 -434
- package/dist/index.js.map +1 -1
- package/dist/viewer/assets/AppDetailPage-Bh4FLKqJ.js +1 -0
- package/dist/viewer/assets/AppDetailPage-BoFB93TE.js +1 -0
- package/dist/viewer/assets/AppDetailPage-Ck_VyLCS.js +1 -0
- package/dist/viewer/assets/BootstrapContext-CL_onTG7.js +1 -0
- package/dist/viewer/assets/BootstrapContext-c_1rWAfc.js +1 -0
- package/dist/viewer/assets/Card-BpDJPrPZ.js +1 -0
- package/dist/viewer/assets/Card-C7KVwKfm.js +1 -0
- package/dist/viewer/assets/CodeStepsPage-BHbbvjvr.js +1 -0
- package/dist/viewer/assets/CodeStepsPage-DnjVkPGB.js +1 -0
- package/dist/viewer/assets/CodeStepsPage-vHoGY4qA.js +1 -0
- package/dist/viewer/assets/CodeViewer--wbTev7Q.js +4 -0
- package/dist/viewer/assets/CodeViewer-Cm7Th9pG.js +4 -0
- package/dist/viewer/assets/CodeViewer-DdKuH37F.css +1 -0
- package/dist/viewer/assets/ConfigurationPage-DAVSSfuO.js +1 -0
- package/dist/viewer/assets/ConfigurationPage-_PScSEXP.js +1 -0
- package/dist/viewer/assets/ConfigurationPage-zSJDg1f4.js +1 -0
- package/dist/viewer/assets/CredentialsContext-Rkt9WDIb.js +1 -0
- package/dist/viewer/assets/CredentialsContext-k-q4M-VC.js +1 -0
- package/dist/viewer/assets/{DevSidebar-BiROAoKF.js → DevSidebar-CKdTotWS.js} +1 -1
- package/dist/viewer/assets/DevSidebar-CcSTChgY.js +1 -0
- package/dist/viewer/assets/IconReferencePage-BhQv3kD4.js +1 -0
- package/dist/viewer/assets/IconReferencePage-C_I2Fxnr.js +1 -0
- package/dist/viewer/assets/PluginIcon-CWkWM-Eu.js +1 -0
- package/dist/viewer/assets/SandboxPage-BMz04QVs.js +1 -0
- package/dist/viewer/assets/SandboxPage-BWtQb8kY.js +1 -0
- package/dist/viewer/assets/SandboxPage-DOBUc8VL.js +1 -0
- package/dist/viewer/assets/SidebarContext-Cn8QRb77.js +1 -0
- package/dist/viewer/assets/SidebarContext-DMhpPBf_.js +1 -0
- package/dist/viewer/assets/SourceBrowserPage-C-IhTiL0.js +1 -0
- package/dist/viewer/assets/SourceBrowserPage-Cr6XOIdV.js +1 -0
- package/dist/viewer/assets/VersionsPage-Bpj4_jPC.js +1 -0
- package/dist/viewer/assets/VersionsPage-DAm4Ozs0.js +1 -0
- package/dist/viewer/assets/VersionsPage-T8tsqgQx.js +1 -0
- package/dist/viewer/assets/api-DFDquUsn.js +1 -0
- package/dist/viewer/assets/api-bszpvP9L.js +1 -0
- package/dist/viewer/assets/bundleQuery-BQLpZ06V.js +1 -0
- package/dist/viewer/assets/bundleQuery-CjbA01rb.js +1 -0
- package/dist/viewer/assets/fa-core-BoBqCOD3.js +558 -0
- package/dist/viewer/assets/fa-core-C4eeAd4v.js +558 -0
- package/dist/viewer/assets/fa-pro-icons-j8-_MCQ7.js +1 -0
- package/dist/viewer/assets/formatBytes-BCP0hIUu.js +1 -0
- package/dist/viewer/assets/index-7CwR8Ogd.js +5 -0
- package/dist/viewer/assets/index-CCB9tECd.css +2 -0
- package/dist/viewer/assets/index-FEIsXxbf.js +5 -0
- package/dist/viewer/assets/index-fU82pXSW.js +5 -0
- package/dist/viewer/assets/{react-D_K8ni3_.js → react-CmC5kGa_.js} +1 -1
- package/dist/viewer/assets/react-csXWLXSE.js +9 -0
- package/dist/viewer/assets/{recordDetail.worker-BIHPFt8Y.js → recordDetail.worker-C0-mQTaj.js} +1 -1
- package/dist/viewer/assets/useCriticalExceptionDialog-BeVl0uaO.js +14 -0
- package/dist/viewer/assets/useCriticalExceptionDialog-DseJ_u9B.js +14 -0
- package/dist/viewer/assets/useCurrentApp-CWtSEVC4.js +1 -0
- package/dist/viewer/assets/useCurrentApp-x1p8URhM.js +1 -0
- package/dist/viewer/assets/useLocalStorage-HYhrPUxS.js +1 -0
- package/dist/viewer/assets/useLocalStorage-eJ1PxgAP.js +1 -0
- package/dist/viewer/assets/useNavigate-BblEmQ3O.js +1 -0
- package/dist/viewer/assets/useNavigate-Ci7hClRo.js +1 -0
- package/dist/viewer/assets/useQuery-5OkVbV1s.js +1 -0
- package/dist/viewer/assets/useQuery-B3Ek2Yet.js +1 -0
- package/dist/viewer/assets/{validIcons-TcFXfn-E.js → validIcons-BzGQ4tWD.js} +1 -1
- package/dist/viewer/assets/validIcons-CzxkK9y8.js +1 -0
- package/dist/viewer/index.html +12 -8
- package/dist/viewer/mark.svg +17 -0
- package/package.json +10 -12
- package/dist/viewer/assets/AppDetailPage-BJrg5Wsz.js +0 -1
- package/dist/viewer/assets/BootstrapContext-D3lhzWap.js +0 -1
- package/dist/viewer/assets/Card-CPMAuqAb.js +0 -1
- package/dist/viewer/assets/ConfigurationPage-D8I8rWjF.js +0 -1
- package/dist/viewer/assets/CredentialsContext-BLZX6RwF.js +0 -1
- package/dist/viewer/assets/SandboxPage-ByixszHi.js +0 -1
- package/dist/viewer/assets/SidebarContext-Byo6PYOF.js +0 -1
- package/dist/viewer/assets/VersionsPage-BKul6bXI.js +0 -1
- package/dist/viewer/assets/fa-core-mHW_3JUd.js +0 -558
- package/dist/viewer/assets/index-BoSQQwMQ.css +0 -2
- package/dist/viewer/assets/index-Bomh3C3X.js +0 -5
- package/dist/viewer/assets/useObjectLookups-Cx6A7PGj.js +0 -14
- /package/dist/viewer/assets/{fa-pro-icons-CIQGXozY.js → fa-pro-icons-Dr3O0gTt.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -89,7 +89,8 @@ async function runBuild(pluginDir, outputDir, onStep) {
|
|
|
89
89
|
return serializePlugin(plugin, allReleaseNotes);
|
|
90
90
|
});
|
|
91
91
|
const jsonStr = JSON.stringify(bundle, null, 2);
|
|
92
|
-
const
|
|
92
|
+
const sizedBundle = bundle.map(({ allReleaseNotes: _notes, ...rest }) => rest);
|
|
93
|
+
const bundleSize = Buffer.byteLength(JSON.stringify(sizedBundle, null, 2), "utf-8");
|
|
93
94
|
await writeFile(join2(outputDir, "bundle.json"), jsonStr, "utf-8");
|
|
94
95
|
return { bundleSize };
|
|
95
96
|
}
|
|
@@ -112,24 +113,28 @@ import { Box as Box2, Text as Text2 } from "ink";
|
|
|
112
113
|
import { Box, Text } from "ink";
|
|
113
114
|
import { jsx } from "react/jsx-runtime";
|
|
114
115
|
var LOGO_LINES = [
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
116
|
+
" :::::::: ",
|
|
117
|
+
" :::::: ::::::::::: ",
|
|
118
|
+
" :::::: :::::::::: ",
|
|
119
|
+
" :::::: :::::: ::::::: ",
|
|
120
|
+
" ::::: :::::: ::::::::::::::::: ",
|
|
121
|
+
" ::::: ::::: ::::::::::::::::::: ",
|
|
122
|
+
" ::::: ::::: :::::: ::::: ",
|
|
123
|
+
":::: ::::: ::: ::::: :: ",
|
|
124
|
+
"::::: :::::: :::::::::: ",
|
|
125
|
+
"::::: :::::: ::::::::::: ",
|
|
126
|
+
" ::::: ::: ::::::: ",
|
|
127
|
+
" ::::::: ::: ::::: ",
|
|
128
|
+
" ::::::::::: :::::: :::::",
|
|
129
|
+
" :::::::::: :::::: :::::",
|
|
130
|
+
" :: ::::: ::: ::::: ::::",
|
|
131
|
+
" ::::: :::::: ::::: ::::: ::: ",
|
|
132
|
+
" ::::::::::::::::::: ::::: ::::: ",
|
|
133
|
+
" ::::::::::::::::: ::::: ::::: ",
|
|
134
|
+
" ::::::::: :::::: :::::: ",
|
|
135
|
+
" :::::::::: :::::: ",
|
|
136
|
+
" ::::::::::: :::::: ",
|
|
137
|
+
" :::::::: "
|
|
133
138
|
];
|
|
134
139
|
var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) });
|
|
135
140
|
|
|
@@ -137,10 +142,7 @@ var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: L
|
|
|
137
142
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
138
143
|
var AppHeader = ({ marginBottom = 0 }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
139
144
|
/* @__PURE__ */ jsx2(Logo, {}),
|
|
140
|
-
/* @__PURE__ */
|
|
141
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }),
|
|
142
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500".repeat(24) })
|
|
143
|
-
] })
|
|
145
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, marginBottom, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Development Toolkit" }) })
|
|
144
146
|
] });
|
|
145
147
|
|
|
146
148
|
// src/ui/Spinner.tsx
|
|
@@ -219,7 +221,7 @@ var BuildUI = ({ outputDir, pluginDir }) => {
|
|
|
219
221
|
] }) }, s);
|
|
220
222
|
}),
|
|
221
223
|
isDone && bundleSize !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
|
|
222
|
-
"\u2713 bundle
|
|
224
|
+
"\u2713 bundle \u2014 ",
|
|
223
225
|
formatBytes(bundleSize)
|
|
224
226
|
] }) }),
|
|
225
227
|
errorMessage !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorMessage }) })
|
|
@@ -256,22 +258,518 @@ function buildCommand(program2) {
|
|
|
256
258
|
});
|
|
257
259
|
}
|
|
258
260
|
|
|
259
|
-
// src/commands/
|
|
261
|
+
// src/commands/create.ts
|
|
260
262
|
import { createElement as createElement2 } from "react";
|
|
261
263
|
import { render as render2 } from "ink";
|
|
262
264
|
|
|
265
|
+
// src/ui/CreateUI.tsx
|
|
266
|
+
import { useCallback, useState as useState3 } from "react";
|
|
267
|
+
import { Box as Box4, Text as Text5, useApp as useApp2, useInput } from "ink";
|
|
268
|
+
import { join as join5 } from "path";
|
|
269
|
+
|
|
270
|
+
// src/lib/createPlugin.ts
|
|
271
|
+
import { access, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
272
|
+
import { join as join4 } from "path";
|
|
273
|
+
async function pathExists(path2) {
|
|
274
|
+
try {
|
|
275
|
+
await access(path2);
|
|
276
|
+
return true;
|
|
277
|
+
} catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function precheckTargetDir(dir) {
|
|
282
|
+
if (await pathExists(join4(dir, "kizen.json"))) {
|
|
283
|
+
return "has-manifest";
|
|
284
|
+
}
|
|
285
|
+
if (await pathExists(join4(dir, ".kizenapp"))) {
|
|
286
|
+
return "has-kizenapp";
|
|
287
|
+
}
|
|
288
|
+
return "ok";
|
|
289
|
+
}
|
|
290
|
+
async function findAvailableSubDir(parentDir, baseName) {
|
|
291
|
+
const base = join4(parentDir, baseName);
|
|
292
|
+
if (!await pathExists(base)) {
|
|
293
|
+
return base;
|
|
294
|
+
}
|
|
295
|
+
for (let i = 1; i < 1e3; i++) {
|
|
296
|
+
const candidate = join4(parentDir, `${baseName}-${String(i)}`);
|
|
297
|
+
if (!await pathExists(candidate)) {
|
|
298
|
+
return candidate;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
throw new Error(`Could not find an available directory name for "${baseName}"`);
|
|
302
|
+
}
|
|
303
|
+
function buildManifest(input) {
|
|
304
|
+
return {
|
|
305
|
+
name: input.name,
|
|
306
|
+
version: "1.0.0",
|
|
307
|
+
published: true,
|
|
308
|
+
api_name: input.apiName,
|
|
309
|
+
external_link: input.externalLink,
|
|
310
|
+
description: input.description,
|
|
311
|
+
entry: "src/",
|
|
312
|
+
engine: "1.0.0",
|
|
313
|
+
release_notes_directory: "releaseNotes/",
|
|
314
|
+
release_environments: ["prod"],
|
|
315
|
+
config_template: {},
|
|
316
|
+
base_config: {},
|
|
317
|
+
developer_business_id: input.developerBusinessId
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
async function createPlugin(input) {
|
|
321
|
+
await mkdir2(input.targetDir, { recursive: true });
|
|
322
|
+
const manifest = buildManifest(input);
|
|
323
|
+
await writeFile2(
|
|
324
|
+
join4(input.targetDir, "kizen.json"),
|
|
325
|
+
JSON.stringify(manifest, null, 2) + "\n",
|
|
326
|
+
"utf-8"
|
|
327
|
+
);
|
|
328
|
+
await mkdir2(join4(input.targetDir, "src"), { recursive: true });
|
|
329
|
+
await mkdir2(join4(input.targetDir, "releaseNotes"), { recursive: true });
|
|
330
|
+
ensureGitignore(input.targetDir);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/ui/CreateUI.tsx
|
|
334
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
335
|
+
var FIELDS = ["name", "apiName", "externalLink", "description", "developerBusinessId"];
|
|
336
|
+
var FIELD_LABELS = {
|
|
337
|
+
name: "Name",
|
|
338
|
+
apiName: "API name",
|
|
339
|
+
externalLink: "External link",
|
|
340
|
+
description: "Description",
|
|
341
|
+
developerBusinessId: "Business ID"
|
|
342
|
+
};
|
|
343
|
+
var REQUIRED_FIELDS = ["name", "apiName"];
|
|
344
|
+
function inferApiName(name) {
|
|
345
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
346
|
+
}
|
|
347
|
+
var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
|
|
348
|
+
function emptyValues(defaultBusinessId) {
|
|
349
|
+
return {
|
|
350
|
+
name: "",
|
|
351
|
+
apiName: "",
|
|
352
|
+
externalLink: "",
|
|
353
|
+
description: "",
|
|
354
|
+
developerBusinessId: defaultBusinessId
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
var CreateUI = ({ parentDir, defaultBusinessId }) => {
|
|
358
|
+
const app = useApp2();
|
|
359
|
+
const [phase, setPhase] = useState3({ type: "target-select", cursor: 0 });
|
|
360
|
+
const [mode, setMode] = useState3("in-place");
|
|
361
|
+
const [values, setValues] = useState3(() => emptyValues(defaultBusinessId));
|
|
362
|
+
const [inputBuffer, setInputBuffer] = useState3("");
|
|
363
|
+
const [targetDir, setTargetDir] = useState3(parentDir);
|
|
364
|
+
const enterFieldEntry = useCallback((nextMode, initialValues) => {
|
|
365
|
+
setMode(nextMode);
|
|
366
|
+
setInputBuffer(initialValues.name);
|
|
367
|
+
setPhase({ type: "field-entry", field: 0, values: initialValues });
|
|
368
|
+
}, []);
|
|
369
|
+
const runPrecheck = useCallback(
|
|
370
|
+
async (dir) => {
|
|
371
|
+
const result = await precheckTargetDir(dir);
|
|
372
|
+
if (result !== "ok") {
|
|
373
|
+
setPhase({ type: "precheck-error", targetDir: dir, reason: result });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
setTargetDir(dir);
|
|
377
|
+
enterFieldEntry("in-place", values);
|
|
378
|
+
},
|
|
379
|
+
[values, enterFieldEntry]
|
|
380
|
+
);
|
|
381
|
+
const resolveSubDirAndConfirm = useCallback(
|
|
382
|
+
async (finalValues) => {
|
|
383
|
+
try {
|
|
384
|
+
const dir = await findAvailableSubDir(parentDir, finalValues.apiName);
|
|
385
|
+
setTargetDir(dir);
|
|
386
|
+
setPhase({ type: "confirm", values: finalValues });
|
|
387
|
+
} catch (err) {
|
|
388
|
+
setPhase({
|
|
389
|
+
type: "error",
|
|
390
|
+
message: err instanceof Error ? err.message : String(err)
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
[parentDir]
|
|
395
|
+
);
|
|
396
|
+
const runWrite = useCallback(async (finalValues, dir) => {
|
|
397
|
+
try {
|
|
398
|
+
await createPlugin({
|
|
399
|
+
targetDir: dir,
|
|
400
|
+
name: finalValues.name,
|
|
401
|
+
apiName: finalValues.apiName,
|
|
402
|
+
externalLink: finalValues.externalLink,
|
|
403
|
+
description: finalValues.description,
|
|
404
|
+
developerBusinessId: finalValues.developerBusinessId
|
|
405
|
+
});
|
|
406
|
+
setPhase({ type: "done", targetDir: dir });
|
|
407
|
+
} catch (err) {
|
|
408
|
+
setPhase({
|
|
409
|
+
type: "error",
|
|
410
|
+
message: err instanceof Error ? err.message : String(err)
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}, []);
|
|
414
|
+
useInput((input, key) => {
|
|
415
|
+
if (key.ctrl && input === "c") {
|
|
416
|
+
app.exit();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (phase.type === "target-select") {
|
|
420
|
+
if (key.upArrow) {
|
|
421
|
+
setPhase({ type: "target-select", cursor: 0 });
|
|
422
|
+
} else if (key.downArrow) {
|
|
423
|
+
setPhase({ type: "target-select", cursor: 1 });
|
|
424
|
+
} else if (key.return) {
|
|
425
|
+
if (phase.cursor === 0) {
|
|
426
|
+
void runPrecheck(parentDir);
|
|
427
|
+
} else {
|
|
428
|
+
enterFieldEntry("sub-dir", values);
|
|
429
|
+
}
|
|
430
|
+
} else if (key.escape) {
|
|
431
|
+
app.exit();
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (phase.type === "precheck-error") {
|
|
436
|
+
if (key.return || key.escape) {
|
|
437
|
+
app.exit();
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (phase.type === "field-entry") {
|
|
442
|
+
const { field, values: current } = phase;
|
|
443
|
+
const fieldName = FIELDS[field];
|
|
444
|
+
if (!fieldName) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (key.escape) {
|
|
448
|
+
app.exit();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (key.backspace || key.delete) {
|
|
452
|
+
setInputBuffer((prev) => prev.slice(0, -1));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (key.upArrow && field > 0) {
|
|
456
|
+
const updated = { ...current, [fieldName]: inputBuffer };
|
|
457
|
+
const prevField = field - 1;
|
|
458
|
+
const prevName = FIELDS[prevField];
|
|
459
|
+
setPhase({ type: "field-entry", field: prevField, values: updated });
|
|
460
|
+
setValues(updated);
|
|
461
|
+
setInputBuffer(prevName ? updated[prevName] : "");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (key.return || key.tab || key.downArrow) {
|
|
465
|
+
const buffer = inputBuffer.trim();
|
|
466
|
+
const isRequired = REQUIRED_FIELDS.includes(fieldName);
|
|
467
|
+
if (isRequired && !buffer) {
|
|
468
|
+
setPhase({
|
|
469
|
+
type: "field-entry",
|
|
470
|
+
field,
|
|
471
|
+
values: current,
|
|
472
|
+
fieldError: `${FIELD_LABELS[fieldName]} is required`
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const valueToStore = fieldName === "description" ? inputBuffer : buffer;
|
|
477
|
+
const updated = { ...current, [fieldName]: valueToStore };
|
|
478
|
+
setValues(updated);
|
|
479
|
+
const nextField = field + 1;
|
|
480
|
+
if (nextField >= FIELDS.length) {
|
|
481
|
+
setInputBuffer("");
|
|
482
|
+
if (mode === "sub-dir") {
|
|
483
|
+
setPhase({ type: "resolving-target" });
|
|
484
|
+
void resolveSubDirAndConfirm(updated);
|
|
485
|
+
} else {
|
|
486
|
+
setPhase({ type: "confirm", values: updated });
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
const nextName = FIELDS[nextField];
|
|
490
|
+
const existing = nextName ? updated[nextName] : "";
|
|
491
|
+
const nextBuffer = nextName === "apiName" && !existing ? inferApiName(updated.name) : existing;
|
|
492
|
+
setPhase({ type: "field-entry", field: nextField, values: updated });
|
|
493
|
+
setInputBuffer(nextBuffer);
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (input && !key.ctrl && !key.meta) {
|
|
498
|
+
setInputBuffer((prev) => prev + input);
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (phase.type === "confirm") {
|
|
503
|
+
if (key.escape) {
|
|
504
|
+
const lastField = FIELDS.length - 1;
|
|
505
|
+
const lastName = FIELDS[lastField];
|
|
506
|
+
setPhase({ type: "field-entry", field: lastField, values: phase.values });
|
|
507
|
+
setInputBuffer(lastName ? phase.values[lastName] : "");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (key.return) {
|
|
511
|
+
setPhase({ type: "writing" });
|
|
512
|
+
void runWrite(phase.values, targetDir);
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (phase.type === "done" || phase.type === "error") {
|
|
517
|
+
if (key.return || key.escape) {
|
|
518
|
+
app.exit();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
if (phase.type === "target-select") {
|
|
523
|
+
const options = [
|
|
524
|
+
{ label: "Use current directory", desc: parentDir },
|
|
525
|
+
{ label: "Create new sub-directory", desc: `${parentDir}/<inferred from API name>` }
|
|
526
|
+
];
|
|
527
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
528
|
+
/* @__PURE__ */ jsx5(AppHeader, {}),
|
|
529
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
530
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Where should the new plugin live?" }),
|
|
531
|
+
options.map((opt, i) => {
|
|
532
|
+
const selected = phase.cursor === i;
|
|
533
|
+
return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
|
|
534
|
+
/* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
|
|
535
|
+
/* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
|
|
536
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
|
|
537
|
+
] }, opt.label);
|
|
538
|
+
})
|
|
539
|
+
] }),
|
|
540
|
+
/* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
|
|
541
|
+
] });
|
|
542
|
+
}
|
|
543
|
+
if (phase.type === "precheck-error") {
|
|
544
|
+
const { targetDir: dir, reason } = phase;
|
|
545
|
+
const conflict = reason === "has-manifest" ? "kizen.json" : ".kizenapp/";
|
|
546
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
547
|
+
/* @__PURE__ */ jsx5(AppHeader, {}),
|
|
548
|
+
/* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Cannot create plugin" }),
|
|
549
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
550
|
+
/* @__PURE__ */ jsxs3(Text5, { children: [
|
|
551
|
+
dir,
|
|
552
|
+
" already contains ",
|
|
553
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: conflict }),
|
|
554
|
+
"."
|
|
555
|
+
] }),
|
|
556
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Remove the conflicting file/directory or choose a different target to avoid overwriting an existing plugin." })
|
|
557
|
+
] }),
|
|
558
|
+
/* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
|
|
559
|
+
] });
|
|
560
|
+
}
|
|
561
|
+
if (phase.type === "field-entry") {
|
|
562
|
+
const { field: activeField, fieldError } = phase;
|
|
563
|
+
const targetLine = mode === "sub-dir" ? `Target: sub-directory of ${parentDir} (inferred from API name)` : `Target: ${targetDir}`;
|
|
564
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
565
|
+
/* @__PURE__ */ jsx5(AppHeader, {}),
|
|
566
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
567
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Plugin details" }),
|
|
568
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: targetLine })
|
|
569
|
+
] }),
|
|
570
|
+
fieldError !== void 0 && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
|
|
571
|
+
"Error: ",
|
|
572
|
+
fieldError
|
|
573
|
+
] }),
|
|
574
|
+
/* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: FIELDS.map((name, i) => {
|
|
575
|
+
const isActive = activeField === i;
|
|
576
|
+
const displayValue = isActive ? inputBuffer : values[name];
|
|
577
|
+
const isRequired = REQUIRED_FIELDS.includes(name);
|
|
578
|
+
const label = `${FIELD_LABELS[name]}${isRequired ? "" : " (optional)"}`;
|
|
579
|
+
return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
|
|
580
|
+
/* @__PURE__ */ jsx5(Box4, { width: 24, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: label }) }),
|
|
581
|
+
/* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
|
|
582
|
+
/* @__PURE__ */ jsx5(Text5, { children: displayValue }),
|
|
583
|
+
isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
|
|
584
|
+
] }, name);
|
|
585
|
+
}) }),
|
|
586
|
+
/* @__PURE__ */ jsx5(Hint, { text: "Enter/Tab/\u2193 next \xB7 \u2191 previous \xB7 Backspace delete \xB7 Esc cancel" })
|
|
587
|
+
] });
|
|
588
|
+
}
|
|
589
|
+
if (phase.type === "confirm") {
|
|
590
|
+
const manifest = buildManifest({
|
|
591
|
+
targetDir,
|
|
592
|
+
name: phase.values.name,
|
|
593
|
+
apiName: phase.values.apiName,
|
|
594
|
+
externalLink: phase.values.externalLink,
|
|
595
|
+
description: phase.values.description,
|
|
596
|
+
developerBusinessId: phase.values.developerBusinessId
|
|
597
|
+
});
|
|
598
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
599
|
+
/* @__PURE__ */ jsx5(AppHeader, {}),
|
|
600
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
601
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Ready to create" }),
|
|
602
|
+
/* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
|
|
603
|
+
"Target: ",
|
|
604
|
+
targetDir
|
|
605
|
+
] })
|
|
606
|
+
] }),
|
|
607
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
608
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "kizen.json preview" }),
|
|
609
|
+
/* @__PURE__ */ jsx5(Text5, { children: JSON.stringify(manifest, null, 2) })
|
|
610
|
+
] }),
|
|
611
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
612
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Also creates:" }),
|
|
613
|
+
/* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
|
|
614
|
+
" ",
|
|
615
|
+
join5(targetDir, "src/")
|
|
616
|
+
] }),
|
|
617
|
+
/* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
|
|
618
|
+
" ",
|
|
619
|
+
join5(targetDir, "releaseNotes/")
|
|
620
|
+
] }),
|
|
621
|
+
/* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
|
|
622
|
+
" ",
|
|
623
|
+
join5(targetDir, ".gitignore"),
|
|
624
|
+
" (adds .kizenapp/)"
|
|
625
|
+
] })
|
|
626
|
+
] }),
|
|
627
|
+
/* @__PURE__ */ jsx5(Hint, { text: "Enter to create \xB7 Esc to go back" })
|
|
628
|
+
] });
|
|
629
|
+
}
|
|
630
|
+
if (phase.type === "resolving-target") {
|
|
631
|
+
return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Finding available sub-directory\u2026" }) });
|
|
632
|
+
}
|
|
633
|
+
if (phase.type === "writing") {
|
|
634
|
+
return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Creating plugin\u2026" }) });
|
|
635
|
+
}
|
|
636
|
+
if (phase.type === "done") {
|
|
637
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
638
|
+
/* @__PURE__ */ jsx5(AppHeader, {}),
|
|
639
|
+
/* @__PURE__ */ jsxs3(Text5, { color: "green", bold: true, children: [
|
|
640
|
+
"Plugin created at ",
|
|
641
|
+
phase.targetDir
|
|
642
|
+
] }),
|
|
643
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
|
|
644
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Next steps:" }),
|
|
645
|
+
/* @__PURE__ */ jsxs3(Text5, { children: [
|
|
646
|
+
" ",
|
|
647
|
+
"cd ",
|
|
648
|
+
phase.targetDir
|
|
649
|
+
] }),
|
|
650
|
+
/* @__PURE__ */ jsxs3(Text5, { children: [
|
|
651
|
+
" ",
|
|
652
|
+
"appbuilder dev"
|
|
653
|
+
] })
|
|
654
|
+
] }),
|
|
655
|
+
/* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
|
|
656
|
+
] });
|
|
657
|
+
}
|
|
658
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
659
|
+
/* @__PURE__ */ jsx5(AppHeader, {}),
|
|
660
|
+
/* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Failed to create plugin" }),
|
|
661
|
+
/* @__PURE__ */ jsx5(Text5, { children: phase.message }),
|
|
662
|
+
/* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
|
|
663
|
+
] });
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// src/lib/credentials.ts
|
|
667
|
+
import { mkdir as mkdir3, readFile as readFile2, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
|
|
668
|
+
import { homedir } from "os";
|
|
669
|
+
import { dirname, join as join6 } from "path";
|
|
670
|
+
var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
|
|
671
|
+
var GLOBAL_CREDENTIALS_DIR = join6(homedir(), ".kizenappbuilder");
|
|
672
|
+
var GLOBAL_CREDENTIALS_PATH = join6(GLOBAL_CREDENTIALS_DIR, "credentials.json");
|
|
673
|
+
var DEFAULT_PROFILE_NAME = "credentials";
|
|
674
|
+
function isValidEnvironment(value) {
|
|
675
|
+
return ENVIRONMENTS.includes(value);
|
|
676
|
+
}
|
|
677
|
+
function parseCredentials(raw) {
|
|
678
|
+
if (typeof raw !== "object" || raw === null) {
|
|
679
|
+
throw new Error("Credentials must be a JSON object");
|
|
680
|
+
}
|
|
681
|
+
const obj = raw;
|
|
682
|
+
const env = obj.environment;
|
|
683
|
+
return {
|
|
684
|
+
apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
|
|
685
|
+
userId: typeof obj.userId === "string" ? obj.userId : "",
|
|
686
|
+
businessId: typeof obj.businessId === "string" ? obj.businessId : "",
|
|
687
|
+
environment: isValidEnvironment(env) ? env : "go"
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
async function loadCredentialsFromFile(filePath) {
|
|
691
|
+
const content = await readFile2(filePath, "utf-8");
|
|
692
|
+
return parseCredentials(JSON.parse(content));
|
|
693
|
+
}
|
|
694
|
+
async function loadGlobalCredentials() {
|
|
695
|
+
try {
|
|
696
|
+
return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
|
|
697
|
+
} catch {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async function saveGlobalCredentials(credentials) {
|
|
702
|
+
await mkdir3(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
|
|
703
|
+
await writeFile3(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
|
|
704
|
+
}
|
|
705
|
+
function getProfilePath(name) {
|
|
706
|
+
return join6(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
|
|
707
|
+
}
|
|
708
|
+
async function listCredentialProfiles() {
|
|
709
|
+
const profiles = [
|
|
710
|
+
{ name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
|
|
711
|
+
];
|
|
712
|
+
try {
|
|
713
|
+
const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
|
|
714
|
+
for (const entry of entries) {
|
|
715
|
+
if (!entry.endsWith(".json")) {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const name = entry.slice(0, -5);
|
|
719
|
+
if (name === DEFAULT_PROFILE_NAME) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
profiles.push({ name, path: join6(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
|
|
723
|
+
}
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
return profiles;
|
|
727
|
+
}
|
|
728
|
+
async function saveCredentialProfile(name, credentials) {
|
|
729
|
+
if (name === DEFAULT_PROFILE_NAME) {
|
|
730
|
+
await saveGlobalCredentials(credentials);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
await mkdir3(GLOBAL_CREDENTIALS_DIR, { recursive: true });
|
|
734
|
+
await writeFile3(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
|
|
735
|
+
}
|
|
736
|
+
async function loadCredentialProfile(name) {
|
|
737
|
+
try {
|
|
738
|
+
return await loadCredentialsFromFile(getProfilePath(name));
|
|
739
|
+
} catch {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/commands/create.ts
|
|
745
|
+
function createCommand(program2) {
|
|
746
|
+
program2.command("create").description("Scaffold a new Kizen plugin project").action(async () => {
|
|
747
|
+
const parentDir = process.cwd();
|
|
748
|
+
const globalCreds = await loadGlobalCredentials();
|
|
749
|
+
const defaultBusinessId = globalCreds?.businessId ?? "";
|
|
750
|
+
const { waitUntilExit } = render2(createElement2(CreateUI, { parentDir, defaultBusinessId }), {
|
|
751
|
+
exitOnCtrlC: false
|
|
752
|
+
});
|
|
753
|
+
await waitUntilExit();
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/commands/dev.ts
|
|
758
|
+
import { createElement as createElement3 } from "react";
|
|
759
|
+
import { render as render3 } from "ink";
|
|
760
|
+
|
|
263
761
|
// src/ui/DevUI.tsx
|
|
264
|
-
import { useCallback as
|
|
265
|
-
import { Box as
|
|
762
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useRef, useState as useState6 } from "react";
|
|
763
|
+
import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
|
|
266
764
|
import { watch } from "fs";
|
|
267
765
|
import { createServer } from "http";
|
|
268
|
-
import { join as
|
|
766
|
+
import { join as join11 } from "path";
|
|
269
767
|
import { WebSocketServer } from "ws";
|
|
270
768
|
|
|
271
769
|
// src/server/requestHandler.ts
|
|
272
770
|
import { createReadStream } from "fs";
|
|
273
|
-
import { access, readFile as readFile4 } from "fs/promises";
|
|
274
|
-
import { dirname as dirname2, extname, join as
|
|
771
|
+
import { access as access3, readdir as readdir3, readFile as readFile4, stat } from "fs/promises";
|
|
772
|
+
import { dirname as dirname2, extname, join as join9, resolve, sep } from "path";
|
|
275
773
|
import { fileURLToPath } from "url";
|
|
276
774
|
|
|
277
775
|
// src/lib/proxyCache.ts
|
|
@@ -338,101 +836,462 @@ var MIME_TYPES = {
|
|
|
338
836
|
".map": "application/json"
|
|
339
837
|
};
|
|
340
838
|
|
|
341
|
-
// src/lib/
|
|
342
|
-
import { mkdir as
|
|
343
|
-
import {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return ENVIRONMENTS.includes(value);
|
|
351
|
-
}
|
|
352
|
-
function parseCredentials(raw) {
|
|
353
|
-
if (typeof raw !== "object" || raw === null) {
|
|
354
|
-
throw new Error("Credentials must be a JSON object");
|
|
839
|
+
// src/lib/config.ts
|
|
840
|
+
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
841
|
+
import { join as join7 } from "path";
|
|
842
|
+
async function loadConfig(outputDir) {
|
|
843
|
+
try {
|
|
844
|
+
const content = await readFile3(join7(outputDir, "config.json"), "utf-8");
|
|
845
|
+
return JSON.parse(content);
|
|
846
|
+
} catch {
|
|
847
|
+
return {};
|
|
355
848
|
}
|
|
356
|
-
const obj = raw;
|
|
357
|
-
const env = obj.environment;
|
|
358
|
-
return {
|
|
359
|
-
apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
|
|
360
|
-
userId: typeof obj.userId === "string" ? obj.userId : "",
|
|
361
|
-
businessId: typeof obj.businessId === "string" ? obj.businessId : "",
|
|
362
|
-
environment: isValidEnvironment(env) ? env : "go"
|
|
363
|
-
};
|
|
364
849
|
}
|
|
365
|
-
async function
|
|
366
|
-
|
|
367
|
-
|
|
850
|
+
async function saveConfig(outputDir, config) {
|
|
851
|
+
await mkdir4(outputDir, { recursive: true });
|
|
852
|
+
await writeFile4(join7(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
|
|
368
853
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
854
|
+
|
|
855
|
+
// src/server/pythonExecutor.ts
|
|
856
|
+
import { spawn } from "child_process";
|
|
857
|
+
import { writeFile as writeFile5, rm, mkdtemp, access as access2 } from "fs/promises";
|
|
858
|
+
import { join as join8 } from "path";
|
|
859
|
+
import { tmpdir } from "os";
|
|
860
|
+
var WRAPPER_SCRIPT = `
|
|
861
|
+
import sys, json, os, traceback
|
|
862
|
+
|
|
863
|
+
# --- Inputs proxy (attribute access) ---
|
|
864
|
+
class _InputsProxy:
|
|
865
|
+
def __init__(self, data):
|
|
866
|
+
object.__setattr__(self, '_data', data)
|
|
867
|
+
|
|
868
|
+
def __getattr__(self, name):
|
|
869
|
+
if name.startswith('_'):
|
|
870
|
+
raise AttributeError(name)
|
|
871
|
+
return self._data.get(name)
|
|
872
|
+
|
|
873
|
+
def __repr__(self):
|
|
874
|
+
return repr(self._data)
|
|
875
|
+
|
|
876
|
+
def __str__(self):
|
|
877
|
+
return str(self._data)
|
|
878
|
+
|
|
879
|
+
def __bool__(self):
|
|
880
|
+
return bool(self._data)
|
|
881
|
+
|
|
882
|
+
inputs = _InputsProxy(json.loads(os.environ.get('__KIZEN_INPUTS__', '{}')))
|
|
883
|
+
|
|
884
|
+
# --- Secrets ---
|
|
885
|
+
secrets = json.loads(os.environ.get('__KIZEN_SECRETS__', '{}'))
|
|
886
|
+
|
|
887
|
+
# --- Outputs proxy ---
|
|
888
|
+
class _OutputsProxy:
|
|
889
|
+
def __init__(self):
|
|
890
|
+
object.__setattr__(self, '_values', {})
|
|
891
|
+
object.__setattr__(self, '_logs', [])
|
|
892
|
+
|
|
893
|
+
def __setattr__(self, name, value):
|
|
894
|
+
if name.startswith('_'):
|
|
895
|
+
object.__setattr__(self, name, value)
|
|
896
|
+
else:
|
|
897
|
+
self._values[name] = value
|
|
898
|
+
|
|
899
|
+
def __getattr__(self, name):
|
|
900
|
+
if name.startswith('_'):
|
|
901
|
+
raise AttributeError(name)
|
|
902
|
+
return self._values.get(name)
|
|
903
|
+
|
|
904
|
+
def log(self, message):
|
|
905
|
+
self._logs.append(str(message))
|
|
906
|
+
|
|
907
|
+
outputs = _OutputsProxy()
|
|
908
|
+
|
|
909
|
+
# --- HTTP request capture (requests library only) ---
|
|
910
|
+
_kizen_http_requests = []
|
|
911
|
+
|
|
912
|
+
def _kizen_install_http_capture():
|
|
913
|
+
try:
|
|
914
|
+
import requests
|
|
915
|
+
except Exception:
|
|
916
|
+
return
|
|
917
|
+
import time, datetime
|
|
918
|
+
|
|
919
|
+
_original_send = requests.Session.send
|
|
920
|
+
|
|
921
|
+
def _kizen_send(self, request, **kwargs):
|
|
922
|
+
submitted_at = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
|
|
923
|
+
start = time.time()
|
|
924
|
+
error_type = None
|
|
925
|
+
response = None
|
|
926
|
+
try:
|
|
927
|
+
response = _original_send(self, request, **kwargs)
|
|
928
|
+
return response
|
|
929
|
+
except Exception as exc:
|
|
930
|
+
error_type = type(exc).__name__
|
|
931
|
+
raise
|
|
932
|
+
finally:
|
|
933
|
+
body = request.body
|
|
934
|
+
if isinstance(body, bytes):
|
|
935
|
+
try:
|
|
936
|
+
body = body.decode('utf-8')
|
|
937
|
+
except Exception:
|
|
938
|
+
body = '<binary>'
|
|
939
|
+
elif body is None:
|
|
940
|
+
body = ''
|
|
941
|
+
|
|
942
|
+
status = 0
|
|
943
|
+
resp_headers = {}
|
|
944
|
+
resp_body = ''
|
|
945
|
+
if response is not None:
|
|
946
|
+
status = response.status_code
|
|
947
|
+
resp_headers = dict(response.headers)
|
|
948
|
+
if kwargs.get('stream'):
|
|
949
|
+
resp_body = '<streamed>'
|
|
950
|
+
else:
|
|
951
|
+
try:
|
|
952
|
+
resp_body = response.text
|
|
953
|
+
except Exception:
|
|
954
|
+
resp_body = '<unreadable>'
|
|
955
|
+
|
|
956
|
+
_kizen_http_requests.append({
|
|
957
|
+
'method': request.method or '',
|
|
958
|
+
'url': request.url or '',
|
|
959
|
+
'headers': dict(request.headers or {}),
|
|
960
|
+
'body': body,
|
|
961
|
+
'requestSubmittedAt': submitted_at,
|
|
962
|
+
'session': True,
|
|
963
|
+
'responseStatusCode': status,
|
|
964
|
+
'responseHeaders': resp_headers,
|
|
965
|
+
'responseBody': resp_body,
|
|
966
|
+
'requestErrorType': error_type,
|
|
967
|
+
'duration': time.time() - start,
|
|
968
|
+
})
|
|
969
|
+
|
|
970
|
+
requests.Session.send = _kizen_send
|
|
971
|
+
|
|
972
|
+
_kizen_install_http_capture()
|
|
973
|
+
|
|
974
|
+
def _kizen_http_summary():
|
|
975
|
+
if not _kizen_http_requests:
|
|
976
|
+
return None
|
|
977
|
+
return {
|
|
978
|
+
'count': len(_kizen_http_requests),
|
|
979
|
+
'notLogged': 0,
|
|
980
|
+
'requests': _kizen_http_requests,
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
# --- Execute user script ---
|
|
984
|
+
try:
|
|
985
|
+
exec(open(os.environ['__KIZEN_SCRIPT_PATH__'], encoding='utf-8').read())
|
|
986
|
+
result = {
|
|
987
|
+
'success': True,
|
|
988
|
+
'outputValues': outputs._values,
|
|
989
|
+
'logs': outputs._logs,
|
|
990
|
+
'error': None,
|
|
991
|
+
'httpRequests': _kizen_http_summary(),
|
|
992
|
+
}
|
|
993
|
+
except Exception:
|
|
994
|
+
# Build a clean traceback that hides the wrapper internals.
|
|
995
|
+
# Keep only frames from the user script (shown as File "<string>")
|
|
996
|
+
# and re-label them with "script.py" for clarity.
|
|
997
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
998
|
+
entries = traceback.extract_tb(exc_tb)
|
|
999
|
+
user_entries = [e for e in entries if e.filename != __file__]
|
|
1000
|
+
for e in user_entries:
|
|
1001
|
+
if e.filename == '<string>':
|
|
1002
|
+
e.filename = 'script.py'
|
|
1003
|
+
lines = ['Traceback (most recent call last):\\n']
|
|
1004
|
+
lines += traceback.format_list(user_entries)
|
|
1005
|
+
lines += traceback.format_exception_only(exc_type, exc_value)
|
|
1006
|
+
result = {
|
|
1007
|
+
'success': False,
|
|
1008
|
+
'outputValues': outputs._values,
|
|
1009
|
+
'logs': outputs._logs,
|
|
1010
|
+
'error': ''.join(lines),
|
|
1011
|
+
'httpRequests': _kizen_http_summary(),
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
# Write results between markers so we can extract them reliably
|
|
1015
|
+
# even if the user script wrote to stdout
|
|
1016
|
+
sys.stdout.write('\\n__KIZEN_RESULT_START__\\n')
|
|
1017
|
+
sys.stdout.write(json.dumps(result, ensure_ascii=False))
|
|
1018
|
+
sys.stdout.write('\\n__KIZEN_RESULT_END__\\n')
|
|
1019
|
+
sys.stdout.flush()
|
|
1020
|
+
`.trim();
|
|
1021
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1022
|
+
var VENV_DIR = join8(process.cwd(), ".kizenapp", "venv");
|
|
1023
|
+
var VENV_PACKAGES = [
|
|
1024
|
+
"awslambdaric",
|
|
1025
|
+
"bcrypt",
|
|
1026
|
+
"fpdf2",
|
|
1027
|
+
"lxml",
|
|
1028
|
+
"msgpack",
|
|
1029
|
+
"numpy",
|
|
1030
|
+
"paramiko",
|
|
1031
|
+
"pyjwt",
|
|
1032
|
+
"pypdf",
|
|
1033
|
+
"pytz",
|
|
1034
|
+
"requests",
|
|
1035
|
+
"tzdata"
|
|
1036
|
+
];
|
|
1037
|
+
var venvReady = null;
|
|
1038
|
+
function resolvePythonBinary(runtime) {
|
|
1039
|
+
const match = /^python-(\d+\.\d+)$/.exec(runtime);
|
|
1040
|
+
if (match?.[1]) {
|
|
1041
|
+
return `python${match[1]}`;
|
|
374
1042
|
}
|
|
1043
|
+
return "python3";
|
|
375
1044
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
1045
|
+
function runCommand(bin, args, onLine) {
|
|
1046
|
+
return new Promise((resolve2, reject) => {
|
|
1047
|
+
const proc = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1048
|
+
const pipeLines = (source, stream) => {
|
|
1049
|
+
const captured = [];
|
|
1050
|
+
let buffer = "";
|
|
1051
|
+
source.on("data", (chunk) => {
|
|
1052
|
+
buffer += chunk.toString("utf-8");
|
|
1053
|
+
let idx = buffer.indexOf("\n");
|
|
1054
|
+
while (idx >= 0) {
|
|
1055
|
+
const line = buffer.slice(0, idx).replace(/\r$/, "");
|
|
1056
|
+
buffer = buffer.slice(idx + 1);
|
|
1057
|
+
captured.push(line);
|
|
1058
|
+
onLine?.(line, stream);
|
|
1059
|
+
idx = buffer.indexOf("\n");
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
source.on("end", () => {
|
|
1063
|
+
if (buffer.length > 0) {
|
|
1064
|
+
const line = buffer.replace(/\r$/, "");
|
|
1065
|
+
captured.push(line);
|
|
1066
|
+
onLine?.(line, stream);
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
return captured;
|
|
1070
|
+
};
|
|
1071
|
+
const stderrCaptured = pipeLines(proc.stderr, "stderr");
|
|
1072
|
+
pipeLines(proc.stdout, "stdout");
|
|
1073
|
+
proc.on("error", reject);
|
|
1074
|
+
proc.on("close", (code) => {
|
|
1075
|
+
if (code === 0) {
|
|
1076
|
+
resolve2();
|
|
1077
|
+
} else {
|
|
1078
|
+
const tail = stderrCaptured.slice(-10).join("\n").trim();
|
|
1079
|
+
const detail = tail.length > 0 ? `: ${tail}` : "";
|
|
1080
|
+
reject(new Error(`${bin} exited with code ${String(code)}${detail}`));
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
379
1084
|
}
|
|
380
|
-
|
|
381
|
-
|
|
1085
|
+
var installListeners = /* @__PURE__ */ new Set();
|
|
1086
|
+
function emitInstall(event) {
|
|
1087
|
+
for (const listener of installListeners) {
|
|
1088
|
+
listener(event);
|
|
1089
|
+
}
|
|
382
1090
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
1091
|
+
function ensureVenv(runtime, listener) {
|
|
1092
|
+
if (listener) {
|
|
1093
|
+
installListeners.add(listener);
|
|
1094
|
+
}
|
|
1095
|
+
if (!venvReady) {
|
|
1096
|
+
venvReady = (async () => {
|
|
1097
|
+
const venvPython = join8(VENV_DIR, "bin", "python");
|
|
1098
|
+
try {
|
|
1099
|
+
await access2(venvPython);
|
|
1100
|
+
} catch {
|
|
1101
|
+
const systemPython = resolvePythonBinary(runtime);
|
|
1102
|
+
await runCommand(systemPython, ["-m", "venv", VENV_DIR]);
|
|
392
1103
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
1104
|
+
emitInstall({ kind: "start" });
|
|
1105
|
+
try {
|
|
1106
|
+
await runCommand(
|
|
1107
|
+
venvPython,
|
|
1108
|
+
["-m", "pip", "install", "--disable-pip-version-check", ...VENV_PACKAGES],
|
|
1109
|
+
(line, stream) => {
|
|
1110
|
+
emitInstall({ kind: "log", line, stream });
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
emitInstall({ kind: "complete" });
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1116
|
+
emitInstall({ kind: "error", message });
|
|
1117
|
+
throw err;
|
|
1118
|
+
} finally {
|
|
1119
|
+
installListeners.clear();
|
|
396
1120
|
}
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
} catch {
|
|
1121
|
+
return venvPython;
|
|
1122
|
+
})();
|
|
400
1123
|
}
|
|
401
|
-
return
|
|
1124
|
+
return venvReady;
|
|
402
1125
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1126
|
+
function parseResult(stdout) {
|
|
1127
|
+
const startMarker = "__KIZEN_RESULT_START__";
|
|
1128
|
+
const endMarker = "__KIZEN_RESULT_END__";
|
|
1129
|
+
const startIdx = stdout.indexOf(startMarker);
|
|
1130
|
+
const endIdx = stdout.indexOf(endMarker);
|
|
1131
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
1132
|
+
return { userStdout: stdout, result: null };
|
|
407
1133
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
async function loadCredentialProfile(name) {
|
|
1134
|
+
const userStdout = stdout.slice(0, startIdx).trimEnd();
|
|
1135
|
+
const jsonStr = stdout.slice(startIdx + startMarker.length, endIdx).trim();
|
|
412
1136
|
try {
|
|
413
|
-
|
|
1137
|
+
const result = JSON.parse(jsonStr);
|
|
1138
|
+
return { userStdout, result };
|
|
414
1139
|
} catch {
|
|
415
|
-
return null;
|
|
1140
|
+
return { userStdout: stdout, result: null };
|
|
416
1141
|
}
|
|
417
1142
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1143
|
+
async function executePythonStep(params) {
|
|
1144
|
+
const timeout = params.timeout ?? DEFAULT_TIMEOUT;
|
|
1145
|
+
const pythonBin = await ensureVenv(params.scriptRuntime, params.onInstallProgress);
|
|
1146
|
+
const start = Date.now();
|
|
1147
|
+
let tmpDir;
|
|
423
1148
|
try {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1149
|
+
tmpDir = await mkdtemp(join8(tmpdir(), "kizen-step-"));
|
|
1150
|
+
const wrapperPath = join8(tmpDir, "wrapper.py");
|
|
1151
|
+
const scriptPath = join8(tmpDir, "script.py");
|
|
1152
|
+
await Promise.all([
|
|
1153
|
+
writeFile5(wrapperPath, WRAPPER_SCRIPT, "utf-8"),
|
|
1154
|
+
writeFile5(scriptPath, params.script, "utf-8")
|
|
1155
|
+
]);
|
|
1156
|
+
const env = {
|
|
1157
|
+
...process.env,
|
|
1158
|
+
__KIZEN_INPUTS__: JSON.stringify(params.inputs),
|
|
1159
|
+
__KIZEN_SECRETS__: JSON.stringify(params.secrets),
|
|
1160
|
+
__KIZEN_SCRIPT_PATH__: scriptPath
|
|
1161
|
+
};
|
|
1162
|
+
const { exitCode, stdout, stderr } = await new Promise((resolve2) => {
|
|
1163
|
+
const stdoutChunks = [];
|
|
1164
|
+
const stderrChunks = [];
|
|
1165
|
+
let settled = false;
|
|
1166
|
+
const proc = spawn(pythonBin, [wrapperPath], {
|
|
1167
|
+
env,
|
|
1168
|
+
cwd: tmpDir,
|
|
1169
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1170
|
+
});
|
|
1171
|
+
const timer = setTimeout(() => {
|
|
1172
|
+
proc.kill("SIGKILL");
|
|
1173
|
+
}, timeout);
|
|
1174
|
+
proc.stdout.on("data", (chunk) => {
|
|
1175
|
+
stdoutChunks.push(chunk);
|
|
1176
|
+
});
|
|
1177
|
+
proc.stderr.on("data", (chunk) => {
|
|
1178
|
+
stderrChunks.push(chunk);
|
|
1179
|
+
});
|
|
1180
|
+
const finish = (code) => {
|
|
1181
|
+
if (settled) {
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
settled = true;
|
|
1185
|
+
clearTimeout(timer);
|
|
1186
|
+
resolve2({
|
|
1187
|
+
exitCode: code ?? 1,
|
|
1188
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
1189
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8")
|
|
1190
|
+
});
|
|
1191
|
+
};
|
|
1192
|
+
proc.on("close", finish);
|
|
1193
|
+
proc.on("error", (err) => {
|
|
1194
|
+
if (settled) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
settled = true;
|
|
1198
|
+
clearTimeout(timer);
|
|
1199
|
+
resolve2({
|
|
1200
|
+
exitCode: 1,
|
|
1201
|
+
stdout: "",
|
|
1202
|
+
stderr: err.message
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
const durationMs = Date.now() - start;
|
|
1207
|
+
const { userStdout, result } = parseResult(stdout);
|
|
1208
|
+
if (result) {
|
|
1209
|
+
const base2 = {
|
|
1210
|
+
success: result.success,
|
|
1211
|
+
outputValues: result.outputValues,
|
|
1212
|
+
logs: result.logs,
|
|
1213
|
+
stdout: userStdout,
|
|
1214
|
+
stderr,
|
|
1215
|
+
exitCode,
|
|
1216
|
+
durationMs
|
|
1217
|
+
};
|
|
1218
|
+
if (result.error !== null) {
|
|
1219
|
+
base2.error = result.error;
|
|
1220
|
+
}
|
|
1221
|
+
if (result.httpRequests) {
|
|
1222
|
+
base2.httpRequests = result.httpRequests;
|
|
1223
|
+
}
|
|
1224
|
+
return base2;
|
|
1225
|
+
}
|
|
1226
|
+
const base = {
|
|
1227
|
+
success: false,
|
|
1228
|
+
outputValues: {},
|
|
1229
|
+
logs: [],
|
|
1230
|
+
stdout: userStdout,
|
|
1231
|
+
stderr,
|
|
1232
|
+
exitCode,
|
|
1233
|
+
durationMs
|
|
1234
|
+
};
|
|
1235
|
+
if (exitCode !== 0) {
|
|
1236
|
+
base.error = `Process exited with code ${String(exitCode)}`;
|
|
1237
|
+
}
|
|
1238
|
+
return base;
|
|
1239
|
+
} finally {
|
|
1240
|
+
if (tmpDir) {
|
|
1241
|
+
void rm(tmpDir, { recursive: true, force: true });
|
|
1242
|
+
}
|
|
428
1243
|
}
|
|
429
1244
|
}
|
|
430
|
-
async function saveConfig(outputDir, config) {
|
|
431
|
-
await mkdir3(outputDir, { recursive: true });
|
|
432
|
-
await writeFile3(join5(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
|
|
433
|
-
}
|
|
434
1245
|
|
|
435
1246
|
// src/server/requestHandler.ts
|
|
1247
|
+
var SOURCE_FILE_MAX_BYTES = 2 * 1024 * 1024;
|
|
1248
|
+
var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", ".gitignore"]);
|
|
1249
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1250
|
+
".ts",
|
|
1251
|
+
".tsx",
|
|
1252
|
+
".js",
|
|
1253
|
+
".jsx",
|
|
1254
|
+
".mjs",
|
|
1255
|
+
".cjs",
|
|
1256
|
+
".json",
|
|
1257
|
+
".md",
|
|
1258
|
+
".mdx",
|
|
1259
|
+
".yml",
|
|
1260
|
+
".yaml",
|
|
1261
|
+
".toml",
|
|
1262
|
+
".css",
|
|
1263
|
+
".scss",
|
|
1264
|
+
".html",
|
|
1265
|
+
".htm",
|
|
1266
|
+
".xml",
|
|
1267
|
+
".svg",
|
|
1268
|
+
".txt",
|
|
1269
|
+
".sh",
|
|
1270
|
+
".py",
|
|
1271
|
+
".env",
|
|
1272
|
+
".gitignore",
|
|
1273
|
+
".prettierrc",
|
|
1274
|
+
".eslintrc"
|
|
1275
|
+
]);
|
|
1276
|
+
function resolveSafePath(pluginDir, relParam) {
|
|
1277
|
+
if (relParam.includes("\0")) {
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
const rel = relParam.replace(/^\/+/, "");
|
|
1281
|
+
const abs = resolve(pluginDir, rel);
|
|
1282
|
+
if (abs !== pluginDir && !abs.startsWith(pluginDir + sep)) {
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
return abs;
|
|
1286
|
+
}
|
|
1287
|
+
function isInsideSkippedDir(pluginDir, abs) {
|
|
1288
|
+
const rel = abs === pluginDir ? "" : abs.slice(pluginDir.length + 1);
|
|
1289
|
+
if (rel === "") {
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
const segments = rel.split(sep);
|
|
1293
|
+
return segments.some((segment) => SKIP_DIRS.has(segment));
|
|
1294
|
+
}
|
|
436
1295
|
function proxyLogEntryToString(entry) {
|
|
437
1296
|
if (entry.kind === "info") {
|
|
438
1297
|
return entry.message;
|
|
@@ -442,17 +1301,17 @@ function proxyLogEntryToString(entry) {
|
|
|
442
1301
|
}
|
|
443
1302
|
function getViewerPath() {
|
|
444
1303
|
const filename = fileURLToPath(import.meta.url);
|
|
445
|
-
return
|
|
1304
|
+
return join9(dirname2(filename), "viewer");
|
|
446
1305
|
}
|
|
447
1306
|
async function fileExists(filePath) {
|
|
448
1307
|
try {
|
|
449
|
-
await
|
|
1308
|
+
await access3(filePath);
|
|
450
1309
|
return true;
|
|
451
1310
|
} catch {
|
|
452
1311
|
return false;
|
|
453
1312
|
}
|
|
454
1313
|
}
|
|
455
|
-
function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange) {
|
|
1314
|
+
function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange, lastPathRef, pluginDir) {
|
|
456
1315
|
const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
|
|
457
1316
|
return (req, res) => {
|
|
458
1317
|
void (async () => {
|
|
@@ -493,7 +1352,7 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
|
|
|
493
1352
|
return;
|
|
494
1353
|
}
|
|
495
1354
|
if (url === "/api/bundle") {
|
|
496
|
-
const bundlePath =
|
|
1355
|
+
const bundlePath = join9(process.cwd(), ".kizenapp", "bundle.json");
|
|
497
1356
|
try {
|
|
498
1357
|
const content = await readFile4(bundlePath, "utf-8");
|
|
499
1358
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
@@ -504,6 +1363,61 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
|
|
|
504
1363
|
}
|
|
505
1364
|
return;
|
|
506
1365
|
}
|
|
1366
|
+
if (url === "/api/last-path" && req.method === "POST") {
|
|
1367
|
+
const chunks = [];
|
|
1368
|
+
for await (const chunk of req) {
|
|
1369
|
+
chunks.push(chunk);
|
|
1370
|
+
}
|
|
1371
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
|
|
1372
|
+
if (typeof body.path === "string") {
|
|
1373
|
+
lastPathRef.current = body.path;
|
|
1374
|
+
const currentConfig = await loadConfig(outputDir);
|
|
1375
|
+
await saveConfig(outputDir, { ...currentConfig, lastPath: body.path });
|
|
1376
|
+
}
|
|
1377
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
1378
|
+
res.end('{"ok":true}');
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (url === "/api/execute-step" && req.method === "POST") {
|
|
1382
|
+
const chunks = [];
|
|
1383
|
+
for await (const chunk of req) {
|
|
1384
|
+
chunks.push(chunk);
|
|
1385
|
+
}
|
|
1386
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
|
|
1387
|
+
if (typeof body.script !== "string") {
|
|
1388
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
1389
|
+
res.end(JSON.stringify({ error: "Missing required field: script" }));
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const stepParams = {
|
|
1393
|
+
script: body.script,
|
|
1394
|
+
scriptRuntime: body.scriptRuntime ?? "python-3.9",
|
|
1395
|
+
inputs: body.inputs ?? {},
|
|
1396
|
+
secrets: body.secrets ?? {},
|
|
1397
|
+
onInstallProgress: (event) => {
|
|
1398
|
+
if (event.kind === "start") {
|
|
1399
|
+
broadcast({ type: "venv-install-start" });
|
|
1400
|
+
} else if (event.kind === "log") {
|
|
1401
|
+
broadcast({
|
|
1402
|
+
type: "venv-install-log",
|
|
1403
|
+
line: event.line,
|
|
1404
|
+
stream: event.stream
|
|
1405
|
+
});
|
|
1406
|
+
} else if (event.kind === "complete") {
|
|
1407
|
+
broadcast({ type: "venv-install-complete" });
|
|
1408
|
+
} else {
|
|
1409
|
+
broadcast({ type: "venv-install-error", message: event.message });
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
if (typeof body.timeout === "number") {
|
|
1414
|
+
stepParams.timeout = body.timeout;
|
|
1415
|
+
}
|
|
1416
|
+
const result = await executePythonStep(stepParams);
|
|
1417
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
1418
|
+
res.end(JSON.stringify(result));
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
507
1421
|
if (url === "/api/proxy-cache/clear") {
|
|
508
1422
|
proxyCache.clear();
|
|
509
1423
|
createProxyLog({ kind: "info", message: "Proxy cache cleared" });
|
|
@@ -567,9 +1481,113 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
|
|
|
567
1481
|
res.end(Buffer.from(await upstream.arrayBuffer()));
|
|
568
1482
|
return;
|
|
569
1483
|
}
|
|
1484
|
+
if (url.startsWith("/api/source/tree") && req.method === "GET") {
|
|
1485
|
+
const query = new URL(url, "http://localhost").searchParams;
|
|
1486
|
+
const relParam = query.get("path") ?? "";
|
|
1487
|
+
const abs = resolveSafePath(pluginDir, relParam);
|
|
1488
|
+
if (abs === null) {
|
|
1489
|
+
res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
|
|
1490
|
+
res.end(JSON.stringify({ error: "Invalid path" }));
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
if (isInsideSkippedDir(pluginDir, abs)) {
|
|
1494
|
+
res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
|
|
1495
|
+
res.end(JSON.stringify({ error: "Path is excluded" }));
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
try {
|
|
1499
|
+
const dirEntries = await readdir3(abs, { withFileTypes: true });
|
|
1500
|
+
const entries = await Promise.all(
|
|
1501
|
+
dirEntries.filter((entry) => {
|
|
1502
|
+
if (entry.isDirectory()) {
|
|
1503
|
+
return !SKIP_DIRS.has(entry.name);
|
|
1504
|
+
}
|
|
1505
|
+
if (entry.isFile()) {
|
|
1506
|
+
return !SKIP_FILES.has(entry.name);
|
|
1507
|
+
}
|
|
1508
|
+
return false;
|
|
1509
|
+
}).map(async (entry) => {
|
|
1510
|
+
const kind = entry.isDirectory() ? "dir" : "file";
|
|
1511
|
+
const result = {
|
|
1512
|
+
name: entry.name,
|
|
1513
|
+
kind
|
|
1514
|
+
};
|
|
1515
|
+
if (kind === "file") {
|
|
1516
|
+
try {
|
|
1517
|
+
const stats = await stat(join9(abs, entry.name));
|
|
1518
|
+
result.size = stats.size;
|
|
1519
|
+
} catch {
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return result;
|
|
1523
|
+
})
|
|
1524
|
+
);
|
|
1525
|
+
entries.sort((a, b) => {
|
|
1526
|
+
if (a.kind !== b.kind) {
|
|
1527
|
+
return a.kind === "dir" ? -1 : 1;
|
|
1528
|
+
}
|
|
1529
|
+
return a.name.localeCompare(b.name);
|
|
1530
|
+
});
|
|
1531
|
+
const normalizedPath = abs === pluginDir ? "" : abs.slice(pluginDir.length + 1);
|
|
1532
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
1533
|
+
res.end(JSON.stringify({ path: normalizedPath, entries }));
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1536
|
+
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
1537
|
+
res.end(JSON.stringify({ error: message }));
|
|
1538
|
+
}
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if (url.startsWith("/api/source/file") && req.method === "GET") {
|
|
1542
|
+
const query = new URL(url, "http://localhost").searchParams;
|
|
1543
|
+
const relParam = query.get("path") ?? "";
|
|
1544
|
+
if (relParam === "") {
|
|
1545
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
1546
|
+
res.end(JSON.stringify({ error: "Missing path" }));
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const abs = resolveSafePath(pluginDir, relParam);
|
|
1550
|
+
if (abs === null) {
|
|
1551
|
+
res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
|
|
1552
|
+
res.end(JSON.stringify({ error: "Invalid path" }));
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
if (isInsideSkippedDir(pluginDir, abs)) {
|
|
1556
|
+
res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
|
|
1557
|
+
res.end(JSON.stringify({ error: "Path is excluded" }));
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
const stats = await stat(abs);
|
|
1562
|
+
if (!stats.isFile()) {
|
|
1563
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
1564
|
+
res.end(JSON.stringify({ error: "Not a file" }));
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (stats.size > SOURCE_FILE_MAX_BYTES) {
|
|
1568
|
+
res.writeHead(413, { "Content-Type": "application/json; charset=utf-8" });
|
|
1569
|
+
res.end(JSON.stringify({ error: "File too large", size: stats.size }));
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
const ext2 = extname(abs).toLowerCase();
|
|
1573
|
+
const explicitMime = MIME_TYPES[ext2];
|
|
1574
|
+
const isKnownText = TEXT_EXTENSIONS.has(ext2);
|
|
1575
|
+
const contentType = explicitMime ?? (isKnownText ? "text/plain; charset=utf-8" : "application/octet-stream");
|
|
1576
|
+
res.writeHead(200, {
|
|
1577
|
+
"Content-Type": contentType,
|
|
1578
|
+
"Content-Length": String(stats.size)
|
|
1579
|
+
});
|
|
1580
|
+
createReadStream(abs).pipe(res);
|
|
1581
|
+
} catch (err) {
|
|
1582
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1583
|
+
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
1584
|
+
res.end(JSON.stringify({ error: message }));
|
|
1585
|
+
}
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
570
1588
|
const rawPath = url === "/" ? "/index.html" : url;
|
|
571
|
-
const filePath =
|
|
572
|
-
const resolvedPath = await fileExists(filePath) ? filePath :
|
|
1589
|
+
const filePath = join9(viewerPath, rawPath);
|
|
1590
|
+
const resolvedPath = await fileExists(filePath) ? filePath : join9(viewerPath, "index.html");
|
|
573
1591
|
const ext = extname(resolvedPath);
|
|
574
1592
|
const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
575
1593
|
res.writeHead(200, { "Content-Type": mimeType });
|
|
@@ -603,7 +1621,7 @@ import { launch } from "chrome-launcher";
|
|
|
603
1621
|
import { createRequire } from "module";
|
|
604
1622
|
import { mkdirSync } from "fs";
|
|
605
1623
|
import { readFile as readFile5 } from "fs/promises";
|
|
606
|
-
import { dirname as dirname3, join as
|
|
1624
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
607
1625
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
608
1626
|
var CDP = createRequire(import.meta.url)("chrome-remote-interface");
|
|
609
1627
|
var CSP_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -682,7 +1700,7 @@ async function serializeArgAsync(client, arg) {
|
|
|
682
1700
|
}
|
|
683
1701
|
async function installDockIcon(client) {
|
|
684
1702
|
try {
|
|
685
|
-
const iconPath =
|
|
1703
|
+
const iconPath = join10(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
|
|
686
1704
|
const icon = await readFile5(iconPath);
|
|
687
1705
|
await client.Browser.setDockTile({ image: icon.toString("base64") });
|
|
688
1706
|
} catch {
|
|
@@ -690,10 +1708,11 @@ async function installDockIcon(client) {
|
|
|
690
1708
|
}
|
|
691
1709
|
async function launchChromeViewer(opts) {
|
|
692
1710
|
mkdirSync(opts.userDataDir, { recursive: true });
|
|
1711
|
+
const baseUrl = `http://localhost:${String(opts.port)}`;
|
|
693
1712
|
const chrome = await launch({
|
|
694
|
-
startingUrl:
|
|
1713
|
+
startingUrl: baseUrl,
|
|
695
1714
|
chromeFlags: [
|
|
696
|
-
`--app
|
|
1715
|
+
`--app=${baseUrl}`,
|
|
697
1716
|
"--no-first-run",
|
|
698
1717
|
"--no-default-browser-check",
|
|
699
1718
|
"--disable-extensions"
|
|
@@ -702,16 +1721,261 @@ async function launchChromeViewer(opts) {
|
|
|
702
1721
|
handleSIGINT: false,
|
|
703
1722
|
logLevel: "silent"
|
|
704
1723
|
});
|
|
1724
|
+
const cdpPort = chrome.port;
|
|
705
1725
|
let client = null;
|
|
706
1726
|
let cookieTimer = null;
|
|
707
|
-
|
|
1727
|
+
let mainTargetId = null;
|
|
1728
|
+
let reconnectTimer = null;
|
|
1729
|
+
let reconnectAttempt = 0;
|
|
1730
|
+
let killed = false;
|
|
1731
|
+
const dbg = (msg) => {
|
|
1732
|
+
opts.debugLog?.(msg);
|
|
1733
|
+
};
|
|
1734
|
+
const vb = (msg) => {
|
|
1735
|
+
if (opts.verbose) {
|
|
1736
|
+
opts.debugLog?.(msg);
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
const clearCookieTimer = () => {
|
|
1740
|
+
if (cookieTimer !== null) {
|
|
1741
|
+
clearInterval(cookieTimer);
|
|
1742
|
+
cookieTimer = null;
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
const isNewTabUrl = (url) => url === "chrome://new-tab-page/" || url === "chrome://newtab/";
|
|
1746
|
+
const setupClient = async (c) => {
|
|
1747
|
+
clearCookieTimer();
|
|
1748
|
+
mainTargetId = null;
|
|
1749
|
+
await installDockIcon(c);
|
|
1750
|
+
await c.Fetch.enable({
|
|
1751
|
+
patterns: KIZEN_DOMAINS.flatMap((d) => [
|
|
1752
|
+
{ urlPattern: `*://${d}/*`, requestStage: "Response" },
|
|
1753
|
+
{ urlPattern: `*://*.${d}/*`, requestStage: "Response" }
|
|
1754
|
+
])
|
|
1755
|
+
});
|
|
1756
|
+
c.on("Fetch.requestPaused", (rawParams) => {
|
|
1757
|
+
void (async () => {
|
|
1758
|
+
const params = rawParams;
|
|
1759
|
+
const { requestId, request, responseHeaders, responseStatusCode } = params;
|
|
1760
|
+
const url = request.url;
|
|
1761
|
+
vb(`Fetch ${String(responseStatusCode ?? "?")} ${url}`);
|
|
1762
|
+
const filtered = [];
|
|
1763
|
+
let rewroteSetCookie = false;
|
|
1764
|
+
for (const h of responseHeaders ?? []) {
|
|
1765
|
+
if (CSP_HEADERS.has(h.name.toLowerCase())) {
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
|
|
1769
|
+
const patched = h.value.split("\n").map((cookie) => {
|
|
1770
|
+
if (!/SameSite=/i.test(cookie)) {
|
|
1771
|
+
return cookie + "; SameSite=None; Secure";
|
|
1772
|
+
}
|
|
1773
|
+
return cookie.replace(/SameSite=\w+/i, "SameSite=None");
|
|
1774
|
+
}).join("\n");
|
|
1775
|
+
filtered.push({ name: h.name, value: patched });
|
|
1776
|
+
rewroteSetCookie = true;
|
|
1777
|
+
} else {
|
|
1778
|
+
filtered.push(h);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
if (rewroteSetCookie) {
|
|
1782
|
+
vb(` rewrote Set-Cookie SameSite on ${url}`);
|
|
1783
|
+
}
|
|
1784
|
+
try {
|
|
1785
|
+
const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
|
|
1786
|
+
await c.Fetch.fulfillRequest({
|
|
1787
|
+
requestId,
|
|
1788
|
+
responseCode: responseStatusCode ?? 200,
|
|
1789
|
+
responseHeaders: filtered,
|
|
1790
|
+
body,
|
|
1791
|
+
...base64Encoded && { base64Encoded: true }
|
|
1792
|
+
});
|
|
1793
|
+
} catch (e) {
|
|
1794
|
+
vb(` fulfillRequest failed (${e.message}), falling through`);
|
|
1795
|
+
try {
|
|
1796
|
+
await c.Fetch.continueRequest({ requestId });
|
|
1797
|
+
} catch (e2) {
|
|
1798
|
+
vb(` continueRequest also failed (${e2.message})`);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
})();
|
|
1802
|
+
});
|
|
1803
|
+
cookieTimer = setInterval(() => {
|
|
1804
|
+
void (async () => {
|
|
1805
|
+
try {
|
|
1806
|
+
const { cookies } = await c.Network.getAllCookies();
|
|
1807
|
+
let patched = 0;
|
|
1808
|
+
for (const cookie of cookies) {
|
|
1809
|
+
if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (cookie.sameSite === "None") {
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
const raw = cookie.domain ?? "";
|
|
1816
|
+
const bare = raw.startsWith(".") ? raw.slice(1) : raw;
|
|
1817
|
+
await c.Network.setCookie({
|
|
1818
|
+
url: `https://${bare}`,
|
|
1819
|
+
name: cookie.name,
|
|
1820
|
+
value: cookie.value,
|
|
1821
|
+
domain: raw,
|
|
1822
|
+
path: cookie.path ?? "/",
|
|
1823
|
+
secure: true,
|
|
1824
|
+
...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
|
|
1825
|
+
...cookie.expirationDate !== void 0 && {
|
|
1826
|
+
expires: cookie.expirationDate
|
|
1827
|
+
},
|
|
1828
|
+
sameSite: "None"
|
|
1829
|
+
});
|
|
1830
|
+
patched++;
|
|
1831
|
+
}
|
|
1832
|
+
if (patched > 0) {
|
|
1833
|
+
vb(`cookie-patch cycle: updated ${String(patched)} cookie(s) to SameSite=None`);
|
|
1834
|
+
}
|
|
1835
|
+
} catch (e) {
|
|
1836
|
+
vb(`cookie-patch cycle failed: ${e.message}`);
|
|
1837
|
+
}
|
|
1838
|
+
})();
|
|
1839
|
+
}, 2e3);
|
|
1840
|
+
await c.send("Runtime.enable", { generatePreview: true });
|
|
1841
|
+
c.on("Target.attachedToTarget", (rawParams) => {
|
|
1842
|
+
const params = rawParams;
|
|
1843
|
+
const { sessionId, targetInfo } = params;
|
|
1844
|
+
vb(`attached session ${sessionId} (${targetInfo.type} ${targetInfo.url})`);
|
|
1845
|
+
void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch((e) => {
|
|
1846
|
+
vb(` Runtime.enable on session ${sessionId} failed: ${e.message}`);
|
|
1847
|
+
});
|
|
1848
|
+
});
|
|
1849
|
+
await c.Target.setAutoAttach({
|
|
1850
|
+
autoAttach: true,
|
|
1851
|
+
waitForDebuggerOnStart: false,
|
|
1852
|
+
flatten: true
|
|
1853
|
+
});
|
|
1854
|
+
await c.Target.setDiscoverTargets({ discover: true });
|
|
1855
|
+
const handleTarget = (targetInfo) => {
|
|
1856
|
+
if (targetInfo.type !== "page") {
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if (isNewTabUrl(targetInfo.url)) {
|
|
1860
|
+
dbg(`closed stray new-tab-page target ${targetInfo.targetId}`);
|
|
1861
|
+
if (mainTargetId !== null) {
|
|
1862
|
+
void c.Target.activateTarget({ targetId: mainTargetId }).catch(() => {
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
|
|
1866
|
+
});
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (mainTargetId === null) {
|
|
1870
|
+
mainTargetId = targetInfo.targetId;
|
|
1871
|
+
dbg(`captured main target ${targetInfo.targetId} (${targetInfo.url})`);
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
c.on("Target.targetCreated", (rawParams) => {
|
|
1875
|
+
const { targetInfo } = rawParams;
|
|
1876
|
+
vb(`target +${targetInfo.type} ${targetInfo.targetId} ${targetInfo.url}`);
|
|
1877
|
+
handleTarget(targetInfo);
|
|
1878
|
+
});
|
|
1879
|
+
c.on("Target.targetInfoChanged", (rawParams) => {
|
|
1880
|
+
const { targetInfo } = rawParams;
|
|
1881
|
+
vb(`target ~${targetInfo.type} ${targetInfo.targetId} ${targetInfo.url}`);
|
|
1882
|
+
handleTarget(targetInfo);
|
|
1883
|
+
});
|
|
1884
|
+
c.on("Runtime.consoleAPICalled", (rawParams) => {
|
|
1885
|
+
void (async () => {
|
|
1886
|
+
const { type, args } = rawParams;
|
|
1887
|
+
if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
|
|
1891
|
+
vb(`console.${level} (${String(args.length)} arg(s))`);
|
|
1892
|
+
const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
|
|
1893
|
+
opts.broadcast({ type: "console-message", level, args: serialized });
|
|
1894
|
+
})().catch((e) => {
|
|
1895
|
+
vb(`console serialization failed: ${e.message}`);
|
|
1896
|
+
});
|
|
1897
|
+
});
|
|
1898
|
+
c.on("disconnect", () => {
|
|
1899
|
+
if (killed || client !== c) {
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
dbg("CDP disconnected");
|
|
1903
|
+
client = null;
|
|
1904
|
+
clearCookieTimer();
|
|
1905
|
+
scheduleReconnect();
|
|
1906
|
+
});
|
|
1907
|
+
};
|
|
1908
|
+
const scheduleReconnect = () => {
|
|
1909
|
+
if (killed || reconnectTimer !== null) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const delay = Math.min(1e3 * 2 ** reconnectAttempt, 1e4);
|
|
1913
|
+
const attemptNumber = reconnectAttempt + 1;
|
|
1914
|
+
reconnectAttempt++;
|
|
1915
|
+
dbg(`CDP reconnect scheduled in ${String(delay)}ms (attempt #${String(attemptNumber)})`);
|
|
1916
|
+
reconnectTimer = setTimeout(() => {
|
|
1917
|
+
reconnectTimer = null;
|
|
1918
|
+
void (async () => {
|
|
1919
|
+
let c;
|
|
1920
|
+
try {
|
|
1921
|
+
c = await CDP({ port: cdpPort });
|
|
1922
|
+
} catch {
|
|
1923
|
+
dbg(`CDP reconnect attempt #${String(attemptNumber)} failed`);
|
|
1924
|
+
scheduleReconnect();
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
if (killed) {
|
|
1928
|
+
try {
|
|
1929
|
+
await c.close();
|
|
1930
|
+
} catch {
|
|
1931
|
+
}
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
client = c;
|
|
1935
|
+
try {
|
|
1936
|
+
await setupClient(c);
|
|
1937
|
+
dbg(`CDP reconnected after ${String(attemptNumber)} attempt(s)`);
|
|
1938
|
+
reconnectAttempt = 0;
|
|
1939
|
+
} catch {
|
|
1940
|
+
if (client === c) {
|
|
1941
|
+
client = null;
|
|
1942
|
+
}
|
|
1943
|
+
try {
|
|
1944
|
+
await c.close();
|
|
1945
|
+
} catch {
|
|
1946
|
+
}
|
|
1947
|
+
dbg(`CDP reconnect attempt #${String(attemptNumber)} failed during setup`);
|
|
1948
|
+
scheduleReconnect();
|
|
1949
|
+
}
|
|
1950
|
+
})();
|
|
1951
|
+
}, delay);
|
|
1952
|
+
};
|
|
708
1953
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
709
1954
|
await new Promise((r) => setTimeout(r, 300));
|
|
1955
|
+
dbg(`CDP connect attempt ${String(attempt + 1)}/10`);
|
|
710
1956
|
try {
|
|
711
|
-
|
|
1957
|
+
const c = await CDP({ port: cdpPort });
|
|
1958
|
+
client = c;
|
|
1959
|
+
if (opts.path) {
|
|
1960
|
+
void c.send("Page.navigate", { url: `${baseUrl}${opts.path}` });
|
|
1961
|
+
}
|
|
1962
|
+
try {
|
|
1963
|
+
await setupClient(c);
|
|
1964
|
+
dbg("CDP connected");
|
|
1965
|
+
} catch {
|
|
1966
|
+
if (client === c) {
|
|
1967
|
+
client = null;
|
|
1968
|
+
}
|
|
1969
|
+
try {
|
|
1970
|
+
await c.close();
|
|
1971
|
+
} catch {
|
|
1972
|
+
}
|
|
1973
|
+
dbg("CDP setup failed after connect");
|
|
1974
|
+
}
|
|
712
1975
|
break;
|
|
713
1976
|
} catch {
|
|
714
1977
|
if (attempt === 9) {
|
|
1978
|
+
dbg("CDP unavailable after 10 attempts \u2014 viewer will run without interception");
|
|
715
1979
|
return {
|
|
716
1980
|
kill: () => {
|
|
717
1981
|
chrome.kill();
|
|
@@ -727,120 +1991,20 @@ async function launchChromeViewer(opts) {
|
|
|
727
1991
|
}
|
|
728
1992
|
};
|
|
729
1993
|
}
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
void (async () => {
|
|
740
|
-
const params = rawParams;
|
|
741
|
-
const { requestId, request, responseHeaders, responseStatusCode } = params;
|
|
742
|
-
const url = request.url;
|
|
743
|
-
const filtered = [];
|
|
744
|
-
for (const h of responseHeaders ?? []) {
|
|
745
|
-
if (CSP_HEADERS.has(h.name.toLowerCase())) {
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
|
|
749
|
-
const patched = h.value.split("\n").map((cookie) => {
|
|
750
|
-
if (!/SameSite=/i.test(cookie)) {
|
|
751
|
-
return cookie + "; SameSite=None; Secure";
|
|
752
|
-
}
|
|
753
|
-
return cookie.replace(/SameSite=\w+/i, "SameSite=None");
|
|
754
|
-
}).join("\n");
|
|
755
|
-
filtered.push({ name: h.name, value: patched });
|
|
756
|
-
} else {
|
|
757
|
-
filtered.push(h);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
try {
|
|
761
|
-
const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
|
|
762
|
-
await c.Fetch.fulfillRequest({
|
|
763
|
-
requestId,
|
|
764
|
-
responseCode: responseStatusCode ?? 200,
|
|
765
|
-
responseHeaders: filtered,
|
|
766
|
-
body,
|
|
767
|
-
...base64Encoded && { base64Encoded: true }
|
|
768
|
-
});
|
|
769
|
-
} catch {
|
|
770
|
-
try {
|
|
771
|
-
await c.Fetch.continueRequest({ requestId });
|
|
772
|
-
} catch {
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
})();
|
|
776
|
-
});
|
|
777
|
-
cookieTimer = setInterval(() => {
|
|
778
|
-
void (async () => {
|
|
1994
|
+
const kill = () => {
|
|
1995
|
+
dbg("CDP kill requested \u2014 tearing down");
|
|
1996
|
+
killed = true;
|
|
1997
|
+
if (reconnectTimer !== null) {
|
|
1998
|
+
clearTimeout(reconnectTimer);
|
|
1999
|
+
reconnectTimer = null;
|
|
2000
|
+
}
|
|
2001
|
+
clearCookieTimer();
|
|
2002
|
+
if (client !== null) {
|
|
779
2003
|
try {
|
|
780
|
-
|
|
781
|
-
for (const cookie of cookies) {
|
|
782
|
-
if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
|
|
783
|
-
continue;
|
|
784
|
-
}
|
|
785
|
-
if (cookie.sameSite === "None") {
|
|
786
|
-
continue;
|
|
787
|
-
}
|
|
788
|
-
const raw = cookie.domain ?? "";
|
|
789
|
-
const bare = raw.startsWith(".") ? raw.slice(1) : raw;
|
|
790
|
-
await c.Network.setCookie({
|
|
791
|
-
url: `https://${bare}`,
|
|
792
|
-
name: cookie.name,
|
|
793
|
-
value: cookie.value,
|
|
794
|
-
domain: raw,
|
|
795
|
-
path: cookie.path ?? "/",
|
|
796
|
-
secure: true,
|
|
797
|
-
...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
|
|
798
|
-
...cookie.expirationDate !== void 0 && {
|
|
799
|
-
expires: cookie.expirationDate
|
|
800
|
-
},
|
|
801
|
-
sameSite: "None"
|
|
802
|
-
});
|
|
803
|
-
}
|
|
2004
|
+
void client.close();
|
|
804
2005
|
} catch {
|
|
805
2006
|
}
|
|
806
|
-
|
|
807
|
-
}, 2e3);
|
|
808
|
-
await c.send("Runtime.enable", { generatePreview: true });
|
|
809
|
-
c.on("Target.attachedToTarget", (rawParams) => {
|
|
810
|
-
const { sessionId } = rawParams;
|
|
811
|
-
void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch(() => {
|
|
812
|
-
});
|
|
813
|
-
});
|
|
814
|
-
await c.Target.setAutoAttach({
|
|
815
|
-
autoAttach: true,
|
|
816
|
-
waitForDebuggerOnStart: false,
|
|
817
|
-
flatten: true
|
|
818
|
-
});
|
|
819
|
-
await c.Target.setDiscoverTargets({ discover: true });
|
|
820
|
-
c.on("Target.targetCreated", (rawParams) => {
|
|
821
|
-
const { targetInfo } = rawParams;
|
|
822
|
-
if (targetInfo.type === "page" && targetInfo.url === "chrome://newtab/") {
|
|
823
|
-
void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
c.on("Runtime.consoleAPICalled", (rawParams) => {
|
|
828
|
-
void (async () => {
|
|
829
|
-
const { type, args } = rawParams;
|
|
830
|
-
if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
|
|
834
|
-
const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
|
|
835
|
-
opts.broadcast({ type: "console-message", level, args: serialized });
|
|
836
|
-
})().catch(() => {
|
|
837
|
-
});
|
|
838
|
-
});
|
|
839
|
-
const kill = () => {
|
|
840
|
-
clearInterval(cookieTimer);
|
|
841
|
-
try {
|
|
842
|
-
void c.close();
|
|
843
|
-
} catch {
|
|
2007
|
+
client = null;
|
|
844
2008
|
}
|
|
845
2009
|
chrome.kill();
|
|
846
2010
|
};
|
|
@@ -848,10 +2012,10 @@ async function launchChromeViewer(opts) {
|
|
|
848
2012
|
}
|
|
849
2013
|
|
|
850
2014
|
// src/hooks/useLogHistory.ts
|
|
851
|
-
import { useCallback, useState as
|
|
2015
|
+
import { useCallback as useCallback2, useState as useState4 } from "react";
|
|
852
2016
|
function useLogHistory(limit) {
|
|
853
|
-
const [history, setHistory] =
|
|
854
|
-
const append =
|
|
2017
|
+
const [history, setHistory] = useState4([]);
|
|
2018
|
+
const append = useCallback2(
|
|
855
2019
|
(message) => {
|
|
856
2020
|
setHistory((h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-limit));
|
|
857
2021
|
},
|
|
@@ -861,29 +2025,29 @@ function useLogHistory(limit) {
|
|
|
861
2025
|
}
|
|
862
2026
|
|
|
863
2027
|
// src/ui/CredentialSetupUI.tsx
|
|
864
|
-
import { useCallback as
|
|
865
|
-
import { Box as
|
|
866
|
-
import { jsx as
|
|
867
|
-
var
|
|
868
|
-
var
|
|
2028
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useState as useState5 } from "react";
|
|
2029
|
+
import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
|
|
2030
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2031
|
+
var FIELDS2 = ["apiKey", "userId", "businessId"];
|
|
2032
|
+
var FIELD_LABELS2 = {
|
|
869
2033
|
apiKey: "API Key",
|
|
870
2034
|
userId: "User ID",
|
|
871
2035
|
businessId: "Business ID"
|
|
872
2036
|
};
|
|
873
|
-
var
|
|
2037
|
+
var Hint2 = ({ text }) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: text });
|
|
874
2038
|
var CredentialSetupUI = ({
|
|
875
2039
|
initialMode,
|
|
876
2040
|
showProfileManager,
|
|
877
2041
|
onComplete,
|
|
878
2042
|
onCancel
|
|
879
2043
|
}) => {
|
|
880
|
-
const [phase, setPhase] =
|
|
2044
|
+
const [phase, setPhase] = useState5(
|
|
881
2045
|
initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
|
|
882
2046
|
);
|
|
883
|
-
const [inputBuffer, setInputBuffer] =
|
|
884
|
-
const [error, setError] =
|
|
885
|
-
const [activeProfileName, setActiveProfileName] =
|
|
886
|
-
const handleModeChosen =
|
|
2047
|
+
const [inputBuffer, setInputBuffer] = useState5("");
|
|
2048
|
+
const [error, setError] = useState5(null);
|
|
2049
|
+
const [activeProfileName, setActiveProfileName] = useState5(void 0);
|
|
2050
|
+
const handleModeChosen = useCallback3(
|
|
887
2051
|
(mode) => {
|
|
888
2052
|
if (mode === "local") {
|
|
889
2053
|
onComplete({ mode: "local", credentials: null });
|
|
@@ -893,7 +2057,7 @@ var CredentialSetupUI = ({
|
|
|
893
2057
|
},
|
|
894
2058
|
[onComplete]
|
|
895
2059
|
);
|
|
896
|
-
const handleProfileChosen =
|
|
2060
|
+
const handleProfileChosen = useCallback3(async (profile) => {
|
|
897
2061
|
if (profile === null) {
|
|
898
2062
|
setPhase({ type: "profile-loading" });
|
|
899
2063
|
const profiles = await listCredentialProfiles();
|
|
@@ -906,7 +2070,7 @@ var CredentialSetupUI = ({
|
|
|
906
2070
|
const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
|
|
907
2071
|
setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
|
|
908
2072
|
}, []);
|
|
909
|
-
const handleSave =
|
|
2073
|
+
const handleSave = useCallback3(
|
|
910
2074
|
async (values2, envCursor2) => {
|
|
911
2075
|
const environment = ENVIRONMENTS[envCursor2] ?? "go";
|
|
912
2076
|
const credentials = {
|
|
@@ -931,7 +2095,7 @@ var CredentialSetupUI = ({
|
|
|
931
2095
|
},
|
|
932
2096
|
[onComplete, activeProfileName]
|
|
933
2097
|
);
|
|
934
|
-
|
|
2098
|
+
useInput2((input, key) => {
|
|
935
2099
|
if (key.ctrl && input === "c") {
|
|
936
2100
|
process.exit(0);
|
|
937
2101
|
}
|
|
@@ -1005,7 +2169,7 @@ var CredentialSetupUI = ({
|
|
|
1005
2169
|
}
|
|
1006
2170
|
if (phase.type === "creds-entry") {
|
|
1007
2171
|
const { field, values: values2, envCursor: envCursor2 } = phase;
|
|
1008
|
-
const isEnvField2 = field ===
|
|
2172
|
+
const isEnvField2 = field === FIELDS2.length;
|
|
1009
2173
|
if (isEnvField2) {
|
|
1010
2174
|
if (key.leftArrow) {
|
|
1011
2175
|
setPhase({
|
|
@@ -1016,13 +2180,13 @@ var CredentialSetupUI = ({
|
|
|
1016
2180
|
setPhase({ ...phase, envCursor: (envCursor2 + 1) % ENVIRONMENTS.length });
|
|
1017
2181
|
} else if (key.upArrow) {
|
|
1018
2182
|
setPhase({ ...phase, field: field - 1 });
|
|
1019
|
-
setInputBuffer(values2[
|
|
2183
|
+
setInputBuffer(values2[FIELDS2[field - 1]] ?? "");
|
|
1020
2184
|
} else if (key.return) {
|
|
1021
2185
|
void handleSave(values2, envCursor2);
|
|
1022
2186
|
}
|
|
1023
2187
|
return;
|
|
1024
2188
|
}
|
|
1025
|
-
const fieldName =
|
|
2189
|
+
const fieldName = FIELDS2[field];
|
|
1026
2190
|
if (!fieldName) {
|
|
1027
2191
|
return;
|
|
1028
2192
|
}
|
|
@@ -1032,13 +2196,13 @@ var CredentialSetupUI = ({
|
|
|
1032
2196
|
const updatedValues = { ...values2, [fieldName]: inputBuffer };
|
|
1033
2197
|
const prevField = field - 1;
|
|
1034
2198
|
setPhase({ ...phase, field: prevField, values: updatedValues });
|
|
1035
|
-
setInputBuffer(updatedValues[
|
|
2199
|
+
setInputBuffer(updatedValues[FIELDS2[prevField]] ?? "");
|
|
1036
2200
|
} else if (key.return || key.tab || key.downArrow) {
|
|
1037
2201
|
const updatedValues = { ...values2, [fieldName]: inputBuffer };
|
|
1038
2202
|
const nextField = field + 1;
|
|
1039
2203
|
setPhase({ ...phase, field: nextField, values: updatedValues });
|
|
1040
|
-
if (nextField <
|
|
1041
|
-
setInputBuffer(updatedValues[
|
|
2204
|
+
if (nextField < FIELDS2.length) {
|
|
2205
|
+
setInputBuffer(updatedValues[FIELDS2[nextField]] ?? "");
|
|
1042
2206
|
} else {
|
|
1043
2207
|
setInputBuffer("");
|
|
1044
2208
|
}
|
|
@@ -1047,7 +2211,7 @@ var CredentialSetupUI = ({
|
|
|
1047
2211
|
}
|
|
1048
2212
|
}
|
|
1049
2213
|
});
|
|
1050
|
-
const loadProfileList =
|
|
2214
|
+
const loadProfileList = useCallback3(async () => {
|
|
1051
2215
|
const profiles = await listCredentialProfiles();
|
|
1052
2216
|
setPhase({ type: "profile-select", profiles, cursor: 0 });
|
|
1053
2217
|
}, []);
|
|
@@ -1072,7 +2236,7 @@ var CredentialSetupUI = ({
|
|
|
1072
2236
|
}, [phase.type]);
|
|
1073
2237
|
if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
|
|
1074
2238
|
const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
|
|
1075
|
-
return /* @__PURE__ */
|
|
2239
|
+
return /* @__PURE__ */ jsx6(Box5, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label }) });
|
|
1076
2240
|
}
|
|
1077
2241
|
if (phase.type === "done") {
|
|
1078
2242
|
return null;
|
|
@@ -1082,41 +2246,41 @@ var CredentialSetupUI = ({
|
|
|
1082
2246
|
{ label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
|
|
1083
2247
|
{ label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
|
|
1084
2248
|
];
|
|
1085
|
-
return /* @__PURE__ */
|
|
1086
|
-
/* @__PURE__ */
|
|
1087
|
-
/* @__PURE__ */
|
|
1088
|
-
/* @__PURE__ */
|
|
2249
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
2250
|
+
/* @__PURE__ */ jsx6(AppHeader, {}),
|
|
2251
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
|
|
2252
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Credential mode" }),
|
|
1089
2253
|
options.map((opt, i) => {
|
|
1090
2254
|
const selected = phase.cursor === i;
|
|
1091
|
-
return /* @__PURE__ */
|
|
1092
|
-
/* @__PURE__ */
|
|
1093
|
-
/* @__PURE__ */
|
|
1094
|
-
/* @__PURE__ */
|
|
2255
|
+
return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
|
|
2256
|
+
/* @__PURE__ */ jsx6(Text6, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
|
|
2257
|
+
/* @__PURE__ */ jsx6(Text6, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
|
|
2258
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: opt.desc })
|
|
1095
2259
|
] }, opt.mode);
|
|
1096
2260
|
})
|
|
1097
2261
|
] }),
|
|
1098
|
-
/* @__PURE__ */
|
|
2262
|
+
/* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
|
|
1099
2263
|
] });
|
|
1100
2264
|
}
|
|
1101
2265
|
if (phase.type === "profile-select") {
|
|
1102
2266
|
const { profiles, cursor } = phase;
|
|
1103
2267
|
const addNewIdx = profiles.length;
|
|
1104
|
-
return /* @__PURE__ */
|
|
1105
|
-
/* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */
|
|
1107
|
-
/* @__PURE__ */
|
|
2268
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
2269
|
+
/* @__PURE__ */ jsx6(AppHeader, {}),
|
|
2270
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
|
|
2271
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Credential profiles" }),
|
|
1108
2272
|
profiles.map((profile, i) => {
|
|
1109
2273
|
const selected = cursor === i;
|
|
1110
|
-
return /* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
1112
|
-
/* @__PURE__ */
|
|
1113
|
-
/* @__PURE__ */
|
|
2274
|
+
return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
|
|
2275
|
+
/* @__PURE__ */ jsx6(Text6, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
|
|
2276
|
+
/* @__PURE__ */ jsx6(Text6, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
|
|
2277
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: profile.path })
|
|
1114
2278
|
] }, profile.name);
|
|
1115
2279
|
}),
|
|
1116
|
-
/* @__PURE__ */
|
|
1117
|
-
/* @__PURE__ */
|
|
1118
|
-
/* @__PURE__ */
|
|
1119
|
-
|
|
2280
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
|
|
2281
|
+
/* @__PURE__ */ jsx6(Text6, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
|
|
2282
|
+
/* @__PURE__ */ jsx6(
|
|
2283
|
+
Text6,
|
|
1120
2284
|
{
|
|
1121
2285
|
bold: cursor === addNewIdx,
|
|
1122
2286
|
...cursor === addNewIdx && { color: "cyan" },
|
|
@@ -1125,69 +2289,69 @@ var CredentialSetupUI = ({
|
|
|
1125
2289
|
)
|
|
1126
2290
|
] })
|
|
1127
2291
|
] }),
|
|
1128
|
-
/* @__PURE__ */
|
|
2292
|
+
/* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
|
|
1129
2293
|
] });
|
|
1130
2294
|
}
|
|
1131
2295
|
if (phase.type === "name-entry") {
|
|
1132
2296
|
const { nameBuffer, nameError } = phase;
|
|
1133
|
-
return /* @__PURE__ */
|
|
1134
|
-
/* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
1136
|
-
nameError && /* @__PURE__ */
|
|
2297
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
2298
|
+
/* @__PURE__ */ jsx6(AppHeader, {}),
|
|
2299
|
+
/* @__PURE__ */ jsx6(Box5, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "New credential profile" }) }),
|
|
2300
|
+
nameError && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
|
|
1137
2301
|
"Error: ",
|
|
1138
2302
|
nameError
|
|
1139
2303
|
] }),
|
|
1140
|
-
/* @__PURE__ */
|
|
1141
|
-
/* @__PURE__ */
|
|
1142
|
-
/* @__PURE__ */
|
|
1143
|
-
/* @__PURE__ */
|
|
1144
|
-
/* @__PURE__ */
|
|
2304
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
|
|
2305
|
+
/* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Profile name" }) }),
|
|
2306
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: ">" }),
|
|
2307
|
+
/* @__PURE__ */ jsx6(Text6, { children: nameBuffer }),
|
|
2308
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2588" })
|
|
1145
2309
|
] }),
|
|
1146
|
-
/* @__PURE__ */
|
|
2310
|
+
/* @__PURE__ */ jsx6(Hint2, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
|
|
1147
2311
|
] });
|
|
1148
2312
|
}
|
|
1149
2313
|
const { field: activeField, values, envCursor } = phase;
|
|
1150
|
-
const isEnvField = activeField ===
|
|
2314
|
+
const isEnvField = activeField === FIELDS2.length;
|
|
1151
2315
|
const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
|
|
1152
|
-
return /* @__PURE__ */
|
|
1153
|
-
/* @__PURE__ */
|
|
1154
|
-
/* @__PURE__ */
|
|
1155
|
-
/* @__PURE__ */
|
|
1156
|
-
/* @__PURE__ */
|
|
2316
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
|
|
2317
|
+
/* @__PURE__ */ jsx6(AppHeader, {}),
|
|
2318
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
|
|
2319
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
|
|
2320
|
+
/* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
|
|
1157
2321
|
"Saved to:",
|
|
1158
2322
|
" ",
|
|
1159
2323
|
activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
|
|
1160
2324
|
] })
|
|
1161
2325
|
] }),
|
|
1162
|
-
error && /* @__PURE__ */
|
|
2326
|
+
error && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
|
|
1163
2327
|
"Error: ",
|
|
1164
2328
|
error
|
|
1165
2329
|
] }),
|
|
1166
|
-
/* @__PURE__ */
|
|
1167
|
-
|
|
2330
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
|
|
2331
|
+
FIELDS2.map((name, i) => {
|
|
1168
2332
|
const isActive = activeField === i;
|
|
1169
2333
|
const displayValue = isActive ? inputBuffer : values[name] ?? "";
|
|
1170
|
-
return /* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1172
|
-
/* @__PURE__ */
|
|
1173
|
-
/* @__PURE__ */
|
|
1174
|
-
isActive && /* @__PURE__ */
|
|
2334
|
+
return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
|
|
2335
|
+
/* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS2[name] }) }),
|
|
2336
|
+
/* @__PURE__ */ jsx6(Text6, { ...isActive && { color: "cyan" }, children: ">" }),
|
|
2337
|
+
/* @__PURE__ */ jsx6(Text6, { children: displayValue }),
|
|
2338
|
+
isActive && /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2588" })
|
|
1175
2339
|
] }, name);
|
|
1176
2340
|
}),
|
|
1177
|
-
/* @__PURE__ */
|
|
1178
|
-
/* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1180
|
-
/* @__PURE__ */
|
|
2341
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
|
|
2342
|
+
/* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
|
|
2343
|
+
/* @__PURE__ */ jsx6(Text6, { ...isEnvField && { color: "cyan" }, children: ">" }),
|
|
2344
|
+
/* @__PURE__ */ jsx6(Box5, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
|
|
1181
2345
|
const isSelected = i === envCursor;
|
|
1182
2346
|
if (isEnvField) {
|
|
1183
|
-
return /* @__PURE__ */
|
|
2347
|
+
return /* @__PURE__ */ jsx6(Text6, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
|
|
1184
2348
|
}
|
|
1185
|
-
return /* @__PURE__ */
|
|
2349
|
+
return /* @__PURE__ */ jsx6(Text6, { dimColor: !isSelected, bold: isSelected, children: env }, env);
|
|
1186
2350
|
}) })
|
|
1187
2351
|
] })
|
|
1188
2352
|
] }),
|
|
1189
|
-
isEnvField ? /* @__PURE__ */
|
|
1190
|
-
/* @__PURE__ */
|
|
2353
|
+
isEnvField ? /* @__PURE__ */ jsx6(Hint2, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
|
|
2354
|
+
/* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
|
|
1191
2355
|
"Profile: ",
|
|
1192
2356
|
profileLabel
|
|
1193
2357
|
] })
|
|
@@ -1195,7 +2359,7 @@ var CredentialSetupUI = ({
|
|
|
1195
2359
|
};
|
|
1196
2360
|
|
|
1197
2361
|
// src/ui/DevUI.tsx
|
|
1198
|
-
import { jsx as
|
|
2362
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1199
2363
|
var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
|
|
1200
2364
|
var DevUI = ({
|
|
1201
2365
|
port,
|
|
@@ -1203,34 +2367,39 @@ var DevUI = ({
|
|
|
1203
2367
|
outputDir,
|
|
1204
2368
|
credentials: initialCredentials,
|
|
1205
2369
|
credentialMode: initialCredentialMode,
|
|
1206
|
-
activeCredentialProfile: initialActiveProfile
|
|
2370
|
+
activeCredentialProfile: initialActiveProfile,
|
|
2371
|
+
lastPath: initialLastPath,
|
|
2372
|
+
debug = false,
|
|
2373
|
+
verbose = false
|
|
1207
2374
|
}) => {
|
|
1208
2375
|
const credentialsRef = useRef(initialCredentials);
|
|
1209
2376
|
const activeProfileRef = useRef(initialActiveProfile);
|
|
1210
|
-
const
|
|
1211
|
-
const [
|
|
2377
|
+
const lastPathRef = useRef(initialLastPath);
|
|
2378
|
+
const [credMode, setCredMode] = useState6("main");
|
|
2379
|
+
const [credentialMode, setCredentialMode] = useState6(
|
|
1212
2380
|
initialCredentialMode
|
|
1213
2381
|
);
|
|
1214
|
-
const [status, setStatus] =
|
|
1215
|
-
const [errorMessage, setErrorMessage] =
|
|
2382
|
+
const [status, setStatus] = useState6("starting");
|
|
2383
|
+
const [errorMessage, setErrorMessage] = useState6(null);
|
|
1216
2384
|
const [serverLogHistory, appendServerLog] = useLogHistory(LOG_LIMIT);
|
|
1217
2385
|
const [buildLogHistory, appendBuildLog] = useLogHistory(LOG_LIMIT);
|
|
1218
|
-
const [
|
|
1219
|
-
const [
|
|
1220
|
-
const [
|
|
1221
|
-
const [
|
|
1222
|
-
const [
|
|
1223
|
-
const [
|
|
1224
|
-
const [
|
|
1225
|
-
const [
|
|
1226
|
-
const [
|
|
2386
|
+
const [cdpLogHistory, appendCdpLog] = useLogHistory(LOG_LIMIT);
|
|
2387
|
+
const [proxyLogHistory, setProxyLogHistory] = useState6([]);
|
|
2388
|
+
const [buildStatus, setBuildStatus] = useState6("pending");
|
|
2389
|
+
const [buildError, setBuildError] = useState6(null);
|
|
2390
|
+
const [lastBuilt, setLastBuilt] = useState6(null);
|
|
2391
|
+
const [lastBundleSize, setLastBundleSize] = useState6(null);
|
|
2392
|
+
const [wsClientCount, setWsClientCount] = useState6(0);
|
|
2393
|
+
const [proxyCacheSize, setProxyCacheSize] = useState6(0);
|
|
2394
|
+
const [proxyCacheHits, setProxyCacheHits] = useState6(0);
|
|
2395
|
+
const [proxyCacheMisses, setProxyCacheMisses] = useState6(0);
|
|
1227
2396
|
const buildingRef = useRef(false);
|
|
1228
2397
|
const viewerLaunchedRef = useRef(false);
|
|
1229
2398
|
const chromeHandleRef = useRef(null);
|
|
1230
2399
|
const debounceTimerRef = useRef(null);
|
|
1231
2400
|
const wsClientsRef = useRef(/* @__PURE__ */ new Set());
|
|
1232
2401
|
const pendingMessagesRef = useRef([]);
|
|
1233
|
-
const broadcast =
|
|
2402
|
+
const broadcast = useCallback4((msg) => {
|
|
1234
2403
|
const json = JSON.stringify(msg);
|
|
1235
2404
|
if (wsClientsRef.current.size === 0) {
|
|
1236
2405
|
pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
|
|
@@ -1243,21 +2412,30 @@ var DevUI = ({
|
|
|
1243
2412
|
}
|
|
1244
2413
|
}, []);
|
|
1245
2414
|
const createServerLog = appendServerLog;
|
|
1246
|
-
const launchChromeViewerCb =
|
|
2415
|
+
const launchChromeViewerCb = useCallback4(() => {
|
|
1247
2416
|
chromeHandleRef.current?.kill();
|
|
1248
2417
|
chromeHandleRef.current = null;
|
|
1249
2418
|
void launchChromeViewer({
|
|
1250
2419
|
port,
|
|
1251
|
-
userDataDir:
|
|
1252
|
-
broadcast
|
|
2420
|
+
userDataDir: join11(outputDir, ".chrome"),
|
|
2421
|
+
broadcast,
|
|
2422
|
+
...lastPathRef.current !== void 0 && { path: lastPathRef.current },
|
|
2423
|
+
...debug && { debugLog: appendCdpLog },
|
|
2424
|
+
...verbose && { verbose: true }
|
|
1253
2425
|
}).then((handle) => {
|
|
1254
2426
|
chromeHandleRef.current = handle;
|
|
1255
|
-
process.on("exit", () => {
|
|
1256
|
-
handle.kill();
|
|
1257
|
-
});
|
|
1258
2427
|
});
|
|
1259
|
-
}, [port, outputDir, broadcast]);
|
|
1260
|
-
|
|
2428
|
+
}, [port, outputDir, broadcast, debug, verbose, appendCdpLog]);
|
|
2429
|
+
useEffect4(() => {
|
|
2430
|
+
const onExit = () => {
|
|
2431
|
+
chromeHandleRef.current?.kill();
|
|
2432
|
+
};
|
|
2433
|
+
process.on("exit", onExit);
|
|
2434
|
+
return () => {
|
|
2435
|
+
process.off("exit", onExit);
|
|
2436
|
+
};
|
|
2437
|
+
}, []);
|
|
2438
|
+
useInput3(
|
|
1261
2439
|
(input, key) => {
|
|
1262
2440
|
if (input === "q" || key.ctrl && input === "c") {
|
|
1263
2441
|
process.exit(0);
|
|
@@ -1278,7 +2456,7 @@ var DevUI = ({
|
|
|
1278
2456
|
viewerLaunchedRef.current = true;
|
|
1279
2457
|
launchChromeViewerCb();
|
|
1280
2458
|
}, [status, launchChromeViewerCb]);
|
|
1281
|
-
const handleCredentialsDone =
|
|
2459
|
+
const handleCredentialsDone = useCallback4(
|
|
1282
2460
|
(result) => {
|
|
1283
2461
|
credentialsRef.current = result.credentials;
|
|
1284
2462
|
activeProfileRef.current = result.profileName;
|
|
@@ -1293,7 +2471,7 @@ var DevUI = ({
|
|
|
1293
2471
|
},
|
|
1294
2472
|
[outputDir, broadcast]
|
|
1295
2473
|
);
|
|
1296
|
-
const createProxyLog =
|
|
2474
|
+
const createProxyLog = useCallback4(
|
|
1297
2475
|
(entry) => {
|
|
1298
2476
|
setProxyLogHistory(
|
|
1299
2477
|
(h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${proxyLogEntryToString(entry)}`].slice(
|
|
@@ -1311,14 +2489,14 @@ var DevUI = ({
|
|
|
1311
2489
|
},
|
|
1312
2490
|
[broadcast]
|
|
1313
2491
|
);
|
|
1314
|
-
const createBuildLog =
|
|
2492
|
+
const createBuildLog = useCallback4(
|
|
1315
2493
|
(message) => {
|
|
1316
2494
|
appendBuildLog(message);
|
|
1317
2495
|
broadcast({ type: "log", message });
|
|
1318
2496
|
},
|
|
1319
2497
|
[appendBuildLog, broadcast]
|
|
1320
2498
|
);
|
|
1321
|
-
const triggerBuild =
|
|
2499
|
+
const triggerBuild = useCallback4(() => {
|
|
1322
2500
|
if (buildingRef.current) {
|
|
1323
2501
|
return;
|
|
1324
2502
|
}
|
|
@@ -1370,7 +2548,7 @@ var DevUI = ({
|
|
|
1370
2548
|
}, [pluginDir, triggerBuild, createBuildLog]);
|
|
1371
2549
|
useEffect4(() => {
|
|
1372
2550
|
const viewerPath = getViewerPath();
|
|
1373
|
-
void fileExists(
|
|
2551
|
+
void fileExists(join11(viewerPath, "index.html")).then((viewerBuilt) => {
|
|
1374
2552
|
if (!viewerBuilt) {
|
|
1375
2553
|
setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
|
|
1376
2554
|
setStatus("error");
|
|
@@ -1384,7 +2562,9 @@ var DevUI = ({
|
|
|
1384
2562
|
activeProfileRef,
|
|
1385
2563
|
outputDir,
|
|
1386
2564
|
broadcast,
|
|
1387
|
-
setProxyCacheSize
|
|
2565
|
+
setProxyCacheSize,
|
|
2566
|
+
lastPathRef,
|
|
2567
|
+
pluginDir
|
|
1388
2568
|
);
|
|
1389
2569
|
const server = createServer(handler);
|
|
1390
2570
|
const wss = new WebSocketServer({ server });
|
|
@@ -1416,9 +2596,9 @@ var DevUI = ({
|
|
|
1416
2596
|
server.close();
|
|
1417
2597
|
};
|
|
1418
2598
|
});
|
|
1419
|
-
}, [port, createServerLog, createProxyLog, broadcast, outputDir]);
|
|
2599
|
+
}, [port, createServerLog, createProxyLog, broadcast, outputDir, pluginDir]);
|
|
1420
2600
|
if (credMode === "editing") {
|
|
1421
|
-
return /* @__PURE__ */
|
|
2601
|
+
return /* @__PURE__ */ jsx7(
|
|
1422
2602
|
CredentialSetupUI,
|
|
1423
2603
|
{
|
|
1424
2604
|
...credentialMode !== void 0 && { initialMode: credentialMode },
|
|
@@ -1432,80 +2612,80 @@ var DevUI = ({
|
|
|
1432
2612
|
}
|
|
1433
2613
|
const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
|
|
1434
2614
|
const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
|
|
1435
|
-
return /* @__PURE__ */
|
|
1436
|
-
/* @__PURE__ */
|
|
1437
|
-
/* @__PURE__ */
|
|
1438
|
-
/* @__PURE__ */
|
|
1439
|
-
/* @__PURE__ */
|
|
1440
|
-
buildStatus === "pending" && /* @__PURE__ */
|
|
1441
|
-
buildStatus === "building" && /* @__PURE__ */
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
2615
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
|
|
2616
|
+
/* @__PURE__ */ jsx7(AppHeader, { marginBottom: 1 }),
|
|
2617
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
2618
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 2, marginBottom: 0, children: [
|
|
2619
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "App Build" }),
|
|
2620
|
+
buildStatus === "pending" && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "waiting\u2026" }),
|
|
2621
|
+
buildStatus === "building" && /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
|
|
2622
|
+
/* @__PURE__ */ jsx7(Spinner, {}),
|
|
2623
|
+
/* @__PURE__ */ jsx7(Text7, { children: "Building plugin package\u2026" })
|
|
1444
2624
|
] }),
|
|
1445
|
-
buildStatus === "done" && /* @__PURE__ */
|
|
1446
|
-
/* @__PURE__ */
|
|
1447
|
-
lastBuilt !== null && /* @__PURE__ */
|
|
2625
|
+
buildStatus === "done" && /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
|
|
2626
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Built" }),
|
|
2627
|
+
lastBuilt !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
1448
2628
|
"at ",
|
|
1449
2629
|
lastBuilt.toLocaleTimeString(),
|
|
1450
2630
|
elapsedLabel
|
|
1451
2631
|
] }),
|
|
1452
|
-
lastBundleSize !== null && /* @__PURE__ */
|
|
2632
|
+
lastBundleSize !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
1453
2633
|
"\xB7 bundle: ",
|
|
1454
2634
|
formatBytes(lastBundleSize)
|
|
1455
2635
|
] })
|
|
1456
2636
|
] }),
|
|
1457
|
-
buildStatus === "error" && /* @__PURE__ */
|
|
2637
|
+
buildStatus === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
|
|
1458
2638
|
"\u2717 ",
|
|
1459
2639
|
buildError ?? "unknown error"
|
|
1460
2640
|
] })
|
|
1461
2641
|
] }),
|
|
1462
|
-
/* @__PURE__ */
|
|
1463
|
-
|
|
2642
|
+
/* @__PURE__ */ jsx7(
|
|
2643
|
+
Box6,
|
|
1464
2644
|
{
|
|
1465
2645
|
flexDirection: "column",
|
|
1466
2646
|
height: LOG_DISPLAY + 2,
|
|
1467
2647
|
borderStyle: "single",
|
|
1468
2648
|
borderColor: "gray",
|
|
1469
2649
|
overflow: "hidden",
|
|
1470
|
-
children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */
|
|
2650
|
+
children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, index))
|
|
1471
2651
|
}
|
|
1472
2652
|
)
|
|
1473
2653
|
] }),
|
|
1474
|
-
/* @__PURE__ */
|
|
1475
|
-
/* @__PURE__ */
|
|
1476
|
-
/* @__PURE__ */
|
|
1477
|
-
status === "starting" && /* @__PURE__ */
|
|
1478
|
-
status === "running" && /* @__PURE__ */
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
2654
|
+
/* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2655
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
|
|
2656
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "Web Server" }),
|
|
2657
|
+
status === "starting" && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "starting\u2026" }),
|
|
2658
|
+
status === "running" && /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
|
|
2659
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Running" }),
|
|
2660
|
+
/* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
1481
2661
|
"\u25CF ",
|
|
1482
2662
|
wsClientCount,
|
|
1483
2663
|
" viewer",
|
|
1484
2664
|
wsClientCount !== 1 ? "s" : ""
|
|
1485
2665
|
] })
|
|
1486
2666
|
] }),
|
|
1487
|
-
status === "error" && /* @__PURE__ */
|
|
2667
|
+
status === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
|
|
1488
2668
|
"\u2717 ",
|
|
1489
2669
|
errorMessage ?? "Server error"
|
|
1490
2670
|
] })
|
|
1491
2671
|
] }),
|
|
1492
|
-
/* @__PURE__ */
|
|
1493
|
-
|
|
2672
|
+
/* @__PURE__ */ jsx7(
|
|
2673
|
+
Box6,
|
|
1494
2674
|
{
|
|
1495
2675
|
flexDirection: "column",
|
|
1496
2676
|
height: LOG_DISPLAY + 2,
|
|
1497
2677
|
borderStyle: "single",
|
|
1498
2678
|
borderColor: "gray",
|
|
1499
2679
|
overflow: "hidden",
|
|
1500
|
-
children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */
|
|
2680
|
+
children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, index))
|
|
1501
2681
|
}
|
|
1502
2682
|
)
|
|
1503
2683
|
] }),
|
|
1504
|
-
/* @__PURE__ */
|
|
1505
|
-
/* @__PURE__ */
|
|
1506
|
-
/* @__PURE__ */
|
|
1507
|
-
/* @__PURE__ */
|
|
1508
|
-
/* @__PURE__ */
|
|
2684
|
+
/* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2685
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
|
|
2686
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "Network Proxy" }),
|
|
2687
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Active" }),
|
|
2688
|
+
/* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
1509
2689
|
"\u25CF ",
|
|
1510
2690
|
proxyCacheSize,
|
|
1511
2691
|
" key",
|
|
@@ -1518,26 +2698,47 @@ var DevUI = ({
|
|
|
1518
2698
|
" miss"
|
|
1519
2699
|
] })
|
|
1520
2700
|
] }),
|
|
1521
|
-
/* @__PURE__ */
|
|
1522
|
-
|
|
2701
|
+
/* @__PURE__ */ jsx7(
|
|
2702
|
+
Box6,
|
|
1523
2703
|
{
|
|
1524
2704
|
flexDirection: "column",
|
|
1525
2705
|
height: LOG_DISPLAY + 2,
|
|
1526
2706
|
borderStyle: "single",
|
|
1527
2707
|
borderColor: "gray",
|
|
1528
2708
|
overflow: "hidden",
|
|
1529
|
-
children: proxyLogHistory.length === 0 ? /* @__PURE__ */
|
|
2709
|
+
children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, i))
|
|
1530
2710
|
}
|
|
1531
2711
|
)
|
|
1532
2712
|
] }),
|
|
1533
|
-
/* @__PURE__ */
|
|
1534
|
-
/* @__PURE__ */
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
2713
|
+
debug && /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2714
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
|
|
2715
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "CDP Debug" }),
|
|
2716
|
+
/* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
2717
|
+
"\u25CF ",
|
|
2718
|
+
cdpLogHistory.length,
|
|
2719
|
+
" event(s)"
|
|
2720
|
+
] })
|
|
2721
|
+
] }),
|
|
2722
|
+
/* @__PURE__ */ jsx7(
|
|
2723
|
+
Box6,
|
|
2724
|
+
{
|
|
2725
|
+
flexDirection: "column",
|
|
2726
|
+
height: LOG_DISPLAY + 2,
|
|
2727
|
+
borderStyle: "single",
|
|
2728
|
+
borderColor: "gray",
|
|
2729
|
+
overflow: "hidden",
|
|
2730
|
+
children: cdpLogHistory.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " No CDP events yet." }) : cdpLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, i))
|
|
2731
|
+
}
|
|
2732
|
+
)
|
|
2733
|
+
] }),
|
|
2734
|
+
/* @__PURE__ */ jsxs5(Box6, { marginTop: 1, gap: 1, children: [
|
|
2735
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "[" }),
|
|
2736
|
+
/* @__PURE__ */ jsx7(Text7, { children: "q" }),
|
|
2737
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "] quit [" }),
|
|
2738
|
+
/* @__PURE__ */ jsx7(Text7, { children: "c" }),
|
|
2739
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "] credentials [" }),
|
|
2740
|
+
/* @__PURE__ */ jsx7(Text7, { children: "v" }),
|
|
2741
|
+
/* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
1541
2742
|
"] ",
|
|
1542
2743
|
wsClientCount === 0 ? "open viewer" : "relaunch viewer"
|
|
1543
2744
|
] })
|
|
@@ -1547,14 +2748,14 @@ var DevUI = ({
|
|
|
1547
2748
|
|
|
1548
2749
|
// src/commands/dev.ts
|
|
1549
2750
|
async function runSetupUI(initialMode) {
|
|
1550
|
-
return new Promise((
|
|
2751
|
+
return new Promise((resolve2) => {
|
|
1551
2752
|
let unmountFn = null;
|
|
1552
2753
|
const handleComplete = (result) => {
|
|
1553
2754
|
unmountFn?.();
|
|
1554
|
-
|
|
2755
|
+
resolve2(result);
|
|
1555
2756
|
};
|
|
1556
|
-
const { unmount } =
|
|
1557
|
-
|
|
2757
|
+
const { unmount } = render3(
|
|
2758
|
+
createElement3(CredentialSetupUI, {
|
|
1558
2759
|
...initialMode !== void 0 && { initialMode },
|
|
1559
2760
|
onComplete: handleComplete
|
|
1560
2761
|
}),
|
|
@@ -1564,66 +2765,75 @@ async function runSetupUI(initialMode) {
|
|
|
1564
2765
|
});
|
|
1565
2766
|
}
|
|
1566
2767
|
function devCommand(program2) {
|
|
1567
|
-
program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").option("-c, --credentials <path>", "path to a credentials JSON file").
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
if (
|
|
2768
|
+
program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").option("-c, --credentials <path>", "path to a credentials JSON file").option("-d, --debug", "Enable CDP lifecycle logs in the TUI").option("-v, --verbose", "Log every CDP event and handled error (implies --debug)").action(
|
|
2769
|
+
async (options) => {
|
|
2770
|
+
const port = parseInt(options.port, 10);
|
|
2771
|
+
const pluginDir = process.cwd();
|
|
2772
|
+
const outputDir = `${pluginDir}/.kizenapp`;
|
|
2773
|
+
ensureGitignore(pluginDir);
|
|
2774
|
+
let credentials = null;
|
|
2775
|
+
let credentialMode;
|
|
2776
|
+
let activeCredentialProfile;
|
|
2777
|
+
const { lastPath } = await loadConfig(outputDir);
|
|
2778
|
+
if (options.credentials) {
|
|
2779
|
+
credentials = await loadCredentialsFromFile(options.credentials);
|
|
2780
|
+
} else {
|
|
2781
|
+
const config = await loadConfig(outputDir);
|
|
2782
|
+
if (config.credentialMode === "local") {
|
|
2783
|
+
credentialMode = "local";
|
|
2784
|
+
} else if (config.credentialMode === "global") {
|
|
2785
|
+
credentialMode = "global";
|
|
2786
|
+
activeCredentialProfile = config.activeCredentialProfile;
|
|
2787
|
+
if (activeCredentialProfile) {
|
|
2788
|
+
credentials = await loadCredentialProfile(activeCredentialProfile);
|
|
2789
|
+
if (!credentials) {
|
|
2790
|
+
credentials = await loadGlobalCredentials();
|
|
2791
|
+
activeCredentialProfile = void 0;
|
|
2792
|
+
}
|
|
2793
|
+
} else {
|
|
1587
2794
|
credentials = await loadGlobalCredentials();
|
|
1588
|
-
|
|
2795
|
+
}
|
|
2796
|
+
if (!credentials) {
|
|
2797
|
+
const result = await runSetupUI("global");
|
|
2798
|
+
credentials = result.credentials;
|
|
2799
|
+
activeCredentialProfile = result.profileName;
|
|
1589
2800
|
}
|
|
1590
2801
|
} else {
|
|
1591
|
-
|
|
1592
|
-
}
|
|
1593
|
-
if (!credentials) {
|
|
1594
|
-
const result = await runSetupUI("global");
|
|
2802
|
+
const result = await runSetupUI();
|
|
1595
2803
|
credentials = result.credentials;
|
|
2804
|
+
credentialMode = result.mode;
|
|
1596
2805
|
activeCredentialProfile = result.profileName;
|
|
2806
|
+
const profile = result.profileName;
|
|
2807
|
+
await saveConfig(outputDir, {
|
|
2808
|
+
credentialMode: result.mode,
|
|
2809
|
+
...profile !== void 0 && { activeCredentialProfile: profile }
|
|
2810
|
+
});
|
|
1597
2811
|
}
|
|
1598
|
-
} else {
|
|
1599
|
-
const result = await runSetupUI();
|
|
1600
|
-
credentials = result.credentials;
|
|
1601
|
-
credentialMode = result.mode;
|
|
1602
|
-
activeCredentialProfile = result.profileName;
|
|
1603
|
-
const profile = result.profileName;
|
|
1604
|
-
await saveConfig(outputDir, {
|
|
1605
|
-
credentialMode: result.mode,
|
|
1606
|
-
...profile !== void 0 && { activeCredentialProfile: profile }
|
|
1607
|
-
});
|
|
1608
2812
|
}
|
|
2813
|
+
const verbose = options.verbose === true;
|
|
2814
|
+
const debug = verbose || options.debug === true;
|
|
2815
|
+
const { waitUntilExit } = render3(
|
|
2816
|
+
createElement3(DevUI, {
|
|
2817
|
+
port,
|
|
2818
|
+
pluginDir,
|
|
2819
|
+
outputDir,
|
|
2820
|
+
credentials,
|
|
2821
|
+
...credentialMode !== void 0 && { credentialMode },
|
|
2822
|
+
...activeCredentialProfile !== void 0 && { activeCredentialProfile },
|
|
2823
|
+
...lastPath !== void 0 && { lastPath },
|
|
2824
|
+
debug,
|
|
2825
|
+
verbose
|
|
2826
|
+
}),
|
|
2827
|
+
{ exitOnCtrlC: false }
|
|
2828
|
+
);
|
|
2829
|
+
await waitUntilExit();
|
|
1609
2830
|
}
|
|
1610
|
-
|
|
1611
|
-
createElement2(DevUI, {
|
|
1612
|
-
port,
|
|
1613
|
-
pluginDir,
|
|
1614
|
-
outputDir,
|
|
1615
|
-
credentials,
|
|
1616
|
-
...credentialMode !== void 0 && { credentialMode },
|
|
1617
|
-
...activeCredentialProfile !== void 0 && { activeCredentialProfile }
|
|
1618
|
-
}),
|
|
1619
|
-
{ exitOnCtrlC: false }
|
|
1620
|
-
);
|
|
1621
|
-
await waitUntilExit();
|
|
1622
|
-
});
|
|
2831
|
+
);
|
|
1623
2832
|
}
|
|
1624
2833
|
|
|
1625
2834
|
// src/index.ts
|
|
1626
2835
|
program.name("appbuilder").description("Kizen plugin app builder").version("0.1.0");
|
|
2836
|
+
createCommand(program);
|
|
1627
2837
|
buildCommand(program);
|
|
1628
2838
|
devCommand(program);
|
|
1629
2839
|
program.parse();
|