@qingflow-tech/qingflow-cli 1.1.34 → 1.1.37
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 +2 -2
- package/npm/lib/runtime.mjs +100 -18
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @qingflow-tech/qingflow-cli@1.1.
|
|
6
|
+
npm install @qingflow-tech/qingflow-cli@1.1.37
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.37 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/npm/lib/runtime.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
const WINDOWS = process.platform === "win32";
|
|
7
|
+
const MIN_PYTHON_VERSION = [3, 11, 0];
|
|
7
8
|
|
|
8
9
|
const PKG_BY_BIN = {
|
|
9
10
|
qingflow: "qingflow-cli",
|
|
@@ -26,11 +27,41 @@ function runChecked(command, args, options = {}) {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function
|
|
30
|
+
function commandResult(command, args) {
|
|
30
31
|
const result = spawnSync(command, args, {
|
|
31
|
-
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
32
34
|
});
|
|
33
|
-
return
|
|
35
|
+
return {
|
|
36
|
+
status: result.status ?? 1,
|
|
37
|
+
error: result.error,
|
|
38
|
+
stdout: result.stdout ?? "",
|
|
39
|
+
stderr: result.stderr ?? "",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseVersion(text) {
|
|
44
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
45
|
+
if (!match) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function versionAtLeast(current, minimum) {
|
|
52
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
53
|
+
if ((current[index] ?? 0) > minimum[index]) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if ((current[index] ?? 0) < minimum[index]) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatVersion(version) {
|
|
64
|
+
return version.join(".");
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
export function getPackageRoot(metaUrl) {
|
|
@@ -449,6 +480,40 @@ export function inspectPythonEnv(packageRoot, commandName = "qingflow-mcp") {
|
|
|
449
480
|
};
|
|
450
481
|
}
|
|
451
482
|
|
|
483
|
+
function getRuntimeProbeModule(commandName) {
|
|
484
|
+
if (commandName === "qingflow") {
|
|
485
|
+
return "qingflow_mcp.cli.main";
|
|
486
|
+
}
|
|
487
|
+
if (commandName === "qingflow-app-user-mcp") {
|
|
488
|
+
return "qingflow_mcp.server_app_user";
|
|
489
|
+
}
|
|
490
|
+
if (commandName === "qingflow-app-builder-mcp") {
|
|
491
|
+
return "qingflow_mcp.server_app_builder";
|
|
492
|
+
}
|
|
493
|
+
return "qingflow_mcp";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function probePythonRuntime(packageRoot, commandName) {
|
|
497
|
+
const python = getVenvPython(packageRoot);
|
|
498
|
+
if (!fs.existsSync(python)) {
|
|
499
|
+
return { ok: false, details: `missing Python executable: ${python}` };
|
|
500
|
+
}
|
|
501
|
+
const moduleName = getRuntimeProbeModule(commandName);
|
|
502
|
+
const result = spawnSync(python, ["-c", `import ${moduleName}`], {
|
|
503
|
+
cwd: packageRoot,
|
|
504
|
+
encoding: "utf8",
|
|
505
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
506
|
+
});
|
|
507
|
+
if (result.error) {
|
|
508
|
+
return { ok: false, details: result.error.message };
|
|
509
|
+
}
|
|
510
|
+
if (result.status !== 0) {
|
|
511
|
+
const details = [result.stderr, result.stdout].filter((value) => typeof value === "string" && value.trim()).join("\n");
|
|
512
|
+
return { ok: false, details: details || `import ${moduleName} failed` };
|
|
513
|
+
}
|
|
514
|
+
return { ok: true, details: "" };
|
|
515
|
+
}
|
|
516
|
+
|
|
452
517
|
function formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap = false } = {}) {
|
|
453
518
|
const problemLines = [];
|
|
454
519
|
for (const problem of runtime.problems) {
|
|
@@ -511,30 +576,38 @@ function findOfflineProjectWheel(findLinksDir) {
|
|
|
511
576
|
export function findPython() {
|
|
512
577
|
const preferred = process.env.QINGFLOW_MCP_PYTHON?.trim();
|
|
513
578
|
const candidates = preferred
|
|
514
|
-
? [{ command: preferred,
|
|
579
|
+
? [{ command: preferred, checkArgs: ["-V"], runArgs: [], label: preferred }]
|
|
515
580
|
: WINDOWS
|
|
516
581
|
? [
|
|
517
|
-
{ command: "py",
|
|
518
|
-
{ command: "python",
|
|
519
|
-
{ command: "python3",
|
|
582
|
+
{ command: "py", checkArgs: ["-3", "-V"], runArgs: ["-3"], label: "py -3" },
|
|
583
|
+
{ command: "python", checkArgs: ["-V"], runArgs: [], label: "python" },
|
|
584
|
+
{ command: "python3", checkArgs: ["-V"], runArgs: [], label: "python3" },
|
|
520
585
|
]
|
|
521
586
|
: [
|
|
522
|
-
{ command: "python3",
|
|
523
|
-
{ command: "python",
|
|
587
|
+
{ command: "python3", checkArgs: ["-V"], runArgs: [], label: "python3" },
|
|
588
|
+
{ command: "python", checkArgs: ["-V"], runArgs: [], label: "python" },
|
|
524
589
|
];
|
|
525
590
|
|
|
591
|
+
const rejected = [];
|
|
526
592
|
for (const candidate of candidates) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
593
|
+
const result = commandResult(candidate.command, candidate.checkArgs);
|
|
594
|
+
if (result.status !== 0) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const version = parseVersion(`${result.stdout}\n${result.stderr}`);
|
|
598
|
+
if (!version) {
|
|
599
|
+
rejected.push(`${candidate.label}: version could not be parsed`);
|
|
600
|
+
continue;
|
|
532
601
|
}
|
|
602
|
+
if (!versionAtLeast(version, MIN_PYTHON_VERSION)) {
|
|
603
|
+
rejected.push(`${candidate.label}: ${formatVersion(version)}`);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
return { command: candidate.command, args: candidate.runArgs, label: `${candidate.label} (${formatVersion(version)})` };
|
|
533
607
|
}
|
|
534
608
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
);
|
|
609
|
+
const rejectedText = rejected.length ? ` Detected but rejected: ${rejected.join(", ")}.` : "";
|
|
610
|
+
throw new Error(`Python ${formatVersion(MIN_PYTHON_VERSION)}+ was not found.${rejectedText} Set QINGFLOW_MCP_PYTHON to a Python 3.11+ executable before running npm install.`);
|
|
538
611
|
}
|
|
539
612
|
|
|
540
613
|
export function ensurePythonEnv(packageRoot, { force = false, commandName = "qingflow-mcp" } = {}) {
|
|
@@ -544,7 +617,11 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
|
|
|
544
617
|
const { packageVersion, serverCommand, stampPath, venvDir, stampVersion } = runtime;
|
|
545
618
|
|
|
546
619
|
if (!force && fs.existsSync(serverCommand) && fs.existsSync(stampPath) && stampVersion && stampVersion === packageVersion) {
|
|
547
|
-
|
|
620
|
+
const probe = probePythonRuntime(packageRoot, commandName);
|
|
621
|
+
if (probe.ok) {
|
|
622
|
+
return serverCommand;
|
|
623
|
+
}
|
|
624
|
+
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
548
625
|
}
|
|
549
626
|
|
|
550
627
|
if (force && fs.existsSync(venvDir)) {
|
|
@@ -571,6 +648,11 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
|
|
|
571
648
|
}
|
|
572
649
|
runChecked(pip, pipArgs, { cwd: packageRoot });
|
|
573
650
|
|
|
651
|
+
const probe = probePythonRuntime(packageRoot, commandName);
|
|
652
|
+
if (!probe.ok) {
|
|
653
|
+
throw new Error(`Bootstrap finished but Python runtime import probe failed:\n${probe.details}`);
|
|
654
|
+
}
|
|
655
|
+
|
|
574
656
|
fs.writeFileSync(
|
|
575
657
|
stampPath,
|
|
576
658
|
JSON.stringify(
|
package/package.json
CHANGED