@netnodeag/kraftwerk 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/inspector/server.js +27 -0
- package/dist/inspector/update.d.ts +33 -0
- package/dist/inspector/update.js +73 -0
- package/inspector/dist/assets/{dist-DJnrih_T.js → dist-BNUGzZS7.js} +1 -1
- package/inspector/dist/assets/{dist-DgaFP-8o.js → dist-BOQhQPN9.js} +1 -1
- package/inspector/dist/assets/{dist-CIjxdHNW.js → dist-C2Mu5bT-.js} +1 -1
- package/inspector/dist/assets/{dist-CGccQQRD.js → dist-CUeUDZ9j.js} +1 -1
- package/inspector/dist/assets/{dist-3XKl7K9N.js → dist-CdyUhzAt.js} +1 -1
- package/inspector/dist/assets/{dist-BPzx-u5t.js → dist-CjMETDfE.js} +1 -1
- package/inspector/dist/assets/{dist-DCZKo3cg.js → dist-CmOlZvwa.js} +1 -1
- package/inspector/dist/assets/{dist-bVw1IlxS.js → dist-D8pjVMTl.js} +1 -1
- package/inspector/dist/assets/{dist-DWGd5Kj_.js → dist-DfAFwiGQ.js} +1 -1
- package/inspector/dist/assets/{dist-CdchVWU8.js → dist-DnmDnMK2.js} +1 -1
- package/inspector/dist/assets/dist-rrIMDK_-.js +1 -0
- package/inspector/dist/assets/{editor-DITzQiO1.js → editor-DJbeG376.js} +3 -3
- package/inspector/dist/assets/index-DQrsXEWt.css +1 -0
- package/inspector/dist/assets/{index-CiJ79dQy.js → index-DgEUOA11.js} +4 -2
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/inspector/dist/assets/dist-BnF1Rm2M.js +0 -1
- package/inspector/dist/assets/index-CS1G-A97.css +0 -1
package/README.md
CHANGED
|
@@ -82,9 +82,15 @@ download per call:
|
|
|
82
82
|
npx @netnodeag/kraftwerk --version
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
Upgrading is the same command with `@latest
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
Upgrading is the same command with `@latest`, or "check for updates" in the
|
|
86
|
+
inspector's info popover: a newer version gets an "update now" that runs the
|
|
87
|
+
install through the server (`POST /api/update`, output in the popover,
|
|
88
|
+
`GET /api/update` for its state) with the npm that belongs to the Node the
|
|
89
|
+
inspector runs on. It is refused inside a container (rebuild the image) and
|
|
90
|
+
when the global folder is not writable by the inspector's user (a system
|
|
91
|
+
Node that needs sudo) — then the command is shown to run by hand. A running
|
|
92
|
+
inspector serves the version it started with and offers a relaunch once a
|
|
93
|
+
newer version is on disk, right after "update now" or after a manual install.
|
|
88
94
|
|
|
89
95
|
```bash
|
|
90
96
|
npm install -g @netnodeag/kraftwerk@latest
|
package/dist/inspector/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { attachmentPath, saveAttachment } from "./chat/store.js";
|
|
|
5
5
|
import { setOutputDir, setProjectRoot, getOutputDir, getProjectRoot } from "./context.js";
|
|
6
6
|
import { resolveProject } from "../config.js";
|
|
7
7
|
import { listRuns, getRun, readRunFile, deleteRun } from "./runs.js";
|
|
8
|
+
import { canSelfUpdate, startUpdate, updateStatus } from "./update.js";
|
|
8
9
|
import { listWorkflows, getWorkflow } from "./workflows.js";
|
|
9
10
|
import { dockerStatus, triggerRun, stopRun } from "./runner.js";
|
|
10
11
|
import { clearNotifications, listNotifications, markNotificationsRead } from "./notifications.js";
|
|
@@ -569,6 +570,32 @@ async function handleApi(req, res, url) {
|
|
|
569
570
|
return json(res, { name, current, latest: "", error: "npm registry unreachable" }, 502);
|
|
570
571
|
}
|
|
571
572
|
}
|
|
573
|
+
// GET /api/update — state and log of the self-update, plus whether one can run here.
|
|
574
|
+
// POST /api/update {version?} — run `npm i -g <name>@<version>`; 409 when refused or already running.
|
|
575
|
+
if (seg.length === 2 && seg[1] === "update") {
|
|
576
|
+
const name = await getPkgName();
|
|
577
|
+
if (method === "GET") {
|
|
578
|
+
const can = canSelfUpdate(name);
|
|
579
|
+
return json(res, { ...updateStatus(), name, available: can.ok, reason: can.ok ? undefined : can.reason, restartable: supervised() });
|
|
580
|
+
}
|
|
581
|
+
if (method === "POST") {
|
|
582
|
+
let version = "latest";
|
|
583
|
+
try {
|
|
584
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
585
|
+
if (typeof body.version === "string" && body.version)
|
|
586
|
+
version = body.version;
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
return json(res, { error: "invalid body" }, 400);
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
return json(res, { ...startUpdate(name, version), name }, 202);
|
|
593
|
+
}
|
|
594
|
+
catch (err) {
|
|
595
|
+
return json(res, { error: err.message }, 409);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
572
599
|
// POST /api/restart — exit with the respawn code; the `kraftwerk ui` supervisor relaunches
|
|
573
600
|
if (seg.length === 2 && seg[1] === "restart" && method === "POST") {
|
|
574
601
|
if (!supervised()) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-update from the inspector: one `npm i -g <name>@<version>` at a time,
|
|
3
|
+
* run with the npm that belongs to the Node this server runs on, its output
|
|
4
|
+
* kept for the UI. The install lands on disk; the existing relaunch
|
|
5
|
+
* (/api/restart, supervised by `kraftwerk ui`) then switches to it.
|
|
6
|
+
*
|
|
7
|
+
* Refused where it cannot work: inside a container (the package is baked
|
|
8
|
+
* into the image — rebuild instead), or when the global folder is not
|
|
9
|
+
* writable by this process (a system Node that needs sudo).
|
|
10
|
+
* KRAFTWERK_NPM points at the npm to use (tests point it at a stub).
|
|
11
|
+
*/
|
|
12
|
+
export interface UpdateStatus {
|
|
13
|
+
state: "idle" | "running" | "done" | "failed";
|
|
14
|
+
/** The version asked for ("latest" or a number). */
|
|
15
|
+
version?: string;
|
|
16
|
+
log: string[];
|
|
17
|
+
startedAt?: string;
|
|
18
|
+
finishedAt?: string;
|
|
19
|
+
exitCode?: number | null;
|
|
20
|
+
}
|
|
21
|
+
/** The npm next to the running node (nvm layouts), else npm on the PATH. */
|
|
22
|
+
export declare function npmCommand(): string;
|
|
23
|
+
/** Where a global install would go, and whether this process may write there. */
|
|
24
|
+
export declare function canSelfUpdate(name: string): {
|
|
25
|
+
ok: true;
|
|
26
|
+
root: string;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function updateStatus(): UpdateStatus;
|
|
32
|
+
/** Start the install; throws when one is running or the environment refuses it. */
|
|
33
|
+
export declare function startUpdate(name: string, version?: string): UpdateStatus;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const MAX_LOG = 200;
|
|
5
|
+
const IN_CONTAINER = existsSync("/.dockerenv") || existsSync("/run/.containerenv");
|
|
6
|
+
let status = { state: "idle", log: [] };
|
|
7
|
+
/** The npm next to the running node (nvm layouts), else npm on the PATH. */
|
|
8
|
+
export function npmCommand() {
|
|
9
|
+
if (process.env.KRAFTWERK_NPM)
|
|
10
|
+
return process.env.KRAFTWERK_NPM;
|
|
11
|
+
const sibling = path.join(path.dirname(process.execPath), "npm");
|
|
12
|
+
return existsSync(sibling) ? sibling : "npm";
|
|
13
|
+
}
|
|
14
|
+
/** Where a global install would go, and whether this process may write there. */
|
|
15
|
+
export function canSelfUpdate(name) {
|
|
16
|
+
if (IN_CONTAINER)
|
|
17
|
+
return { ok: false, reason: "running in a container — rebuild the image instead" };
|
|
18
|
+
const r = spawnSync(npmCommand(), ["root", "-g"], { encoding: "utf8", timeout: 15_000 });
|
|
19
|
+
const root = (r.stdout ?? "").trim();
|
|
20
|
+
if (r.status !== 0 || !root)
|
|
21
|
+
return { ok: false, reason: "could not find the global npm folder" };
|
|
22
|
+
// The package folder if it exists, else the global root it would be created in.
|
|
23
|
+
const target = existsSync(path.join(root, name)) ? path.join(root, name) : root;
|
|
24
|
+
try {
|
|
25
|
+
accessSync(target, constants.W_OK);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { ok: false, reason: `${target} is not writable by this process — run the install with the rights it needs` };
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, root };
|
|
31
|
+
}
|
|
32
|
+
export function updateStatus() {
|
|
33
|
+
return { ...status, log: [...status.log] };
|
|
34
|
+
}
|
|
35
|
+
/** Start the install; throws when one is running or the environment refuses it. */
|
|
36
|
+
export function startUpdate(name, version = "latest") {
|
|
37
|
+
if (status.state === "running")
|
|
38
|
+
throw new Error("an update is already running");
|
|
39
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9.\-]*$/.test(version))
|
|
40
|
+
throw new Error("invalid version");
|
|
41
|
+
const can = canSelfUpdate(name);
|
|
42
|
+
if (!can.ok)
|
|
43
|
+
throw new Error(can.reason);
|
|
44
|
+
const spec = `${name}@${version}`;
|
|
45
|
+
status = { state: "running", version, log: [`$ npm i -g ${spec}`], startedAt: new Date().toISOString() };
|
|
46
|
+
const push = (chunk) => {
|
|
47
|
+
for (const line of String(chunk).split(/\r?\n/)) {
|
|
48
|
+
if (!line.trim())
|
|
49
|
+
continue;
|
|
50
|
+
status.log.push(line);
|
|
51
|
+
if (status.log.length > MAX_LOG)
|
|
52
|
+
status.log.splice(0, status.log.length - MAX_LOG);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
let child;
|
|
56
|
+
try {
|
|
57
|
+
child = spawn(npmCommand(), ["i", "-g", spec], {
|
|
58
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
59
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
status = { ...status, state: "failed", finishedAt: new Date().toISOString(), exitCode: null, log: [...status.log, err.message] };
|
|
64
|
+
return updateStatus();
|
|
65
|
+
}
|
|
66
|
+
child.stdout?.on("data", push);
|
|
67
|
+
child.stderr?.on("data", push);
|
|
68
|
+
child.on("error", (err) => push(err.message));
|
|
69
|
+
child.on("close", (code) => {
|
|
70
|
+
status = { ...status, state: code === 0 ? "done" : "failed", finishedAt: new Date().toISOString(), exitCode: code };
|
|
71
|
+
});
|
|
72
|
+
return updateStatus();
|
|
73
|
+
}
|