Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import test from 'node:test';import assert from 'node:assert/strict';
import {mkdir,writeFile,rm,readFile,chmod} from 'node:fs/promises';
import {tmpdir} from 'node:os';import {join} from 'node:path';
import {resolveMcode} from '../src/mcode-location.mjs';
// Windows launcher resolution is exercised from POSIX by faking the on-disk
// layout and passing platform:'win32' + a controlled PATH/PATHEXT: resolution
// is pure filesystem probing, so the real bug (mixed-install layouts) is
// reproducible without a Windows host. Real-Windows behavior is covered by the
// repository's windows CI job.
async function layout(){
const root=await mkdir(join(tmpdir(),`wf-winloc-${crypto.randomUUID()}`),{recursive:true});
const shim=join(root,'shim'),official=join(root,'official');
const mk=async(dir,rel,content)=>{const file=join(dir,...rel);await mkdir(join(file,'..'),{recursive:true});await writeFile(file,content);return file;};
return {root,shim,official,mk,cleanup:async()=>{await rm(root,{recursive:true,force:true});}};
}
const pkg=version=>JSON.stringify({name:'@minimax-ai/code',version});
async function install(dir,{version,withCmd=true,withPs1=true,npmLayout=false}={}){
await mkdir(dir,{recursive:true});
if(withCmd){await writeFile(join(dir,'mcode.cmd'),'@echo off');await writeFile(join(dir,'powershell.exe'),'fake');}
if(withPs1)await writeFile(join(dir,'mcode.ps1'),'# launcher');
if(version){const rel=npmLayout?['node_modules','@minimax-ai','code']:['lib','node_modules','@minimax-ai','code'];
await mkdir(join(dir,...rel),{recursive:true});
await writeFile(join(dir,...rel,'cli.js'),'#!/usr/bin/env node');
await writeFile(join(dir,...rel,'package.json'),pkg(version));}
}
const winEnv=(shim)=>({PATH:`${shim};C:\\Windows\\System32`,PATHEXT:'.cmd;.bat',SystemRoot:'C:\\Windows'});
test('mixed installs: ps1 present, old npm-shim beside a newer official install -> direct node entry of the newest',async()=>{
const f=await layout();try{
await install(f.shim,{version:'0.2.7',npmLayout:true}); // PATH shim with old sibling cli.js
await install(join(f.official,'.minimax-code'),{version:'0.4.12'}); // official layout, newer
const r=await resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'});
assert.ok(r,'resolves');assert.equal(r.command,process.execPath,'no powershell -File hop');
assert.match(r.args[0],/lib[\\/]node_modules.*cli\.js$/);assert.match(r.args[0],/0?4\.12|^.*official/);
assert.equal(JSON.parse(await readFile(join(r.args[0],'..','package.json'),'utf8')).version,'0.4.12','newest wins');
}finally{await f.cleanup();}
});
test('ps1 renamed away, old npm-shim sibling remains -> newest official entry, never the stale sibling',async()=>{
const f=await layout();try{
await install(f.shim,{version:'0.2.7',npmLayout:true,withPs1:false});
await install(join(f.official,'.minimax-code'),{version:'0.4.12'});
const r=await resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'});
assert.equal(JSON.parse(await readFile(join(r.args[0],'..','package.json'),'utf8')).version,'0.4.12');
}finally{await f.cleanup();}
});
test('newest wins regardless of origin: newer npm-shim sibling beats older official',async()=>{
const f=await layout();try{
await install(f.shim,{version:'0.5.0',npmLayout:true});
await install(join(f.official,'.minimax-code'),{version:'0.4.12'});
const r=await resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'});
assert.equal(JSON.parse(await readFile(join(r.args[0],'..','package.json'),'utf8')).version,'0.5.0');
}finally{await f.cleanup();}
});
test('single npm layout without official install -> sibling entry via direct node',async()=>{
const f=await layout();try{
await install(f.shim,{version:'0.4.12',npmLayout:true});
const r=await resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'});
assert.equal(r.command,process.execPath);assert.match(r.args[0],/node_modules.*cli\.js$/);
}finally{await f.cleanup();}
});
test('no node entry anywhere but ps1 exists -> documented powershell -File last resort',async()=>{
const f=await layout();try{
await install(f.shim,{}); // cmd + ps1, no cli.js anywhere
const r=await resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'});
assert.match(r.command,/powershell|pwsh/i);assert.deepEqual(r.args.slice(0,3),['-NoProfile','-File',join(f.shim,'mcode.ps1')]);
}finally{await f.cleanup();}
});
test('cmd without any entry and without ps1 still fails with the repair hint',async()=>{
const f=await layout();try{
await install(f.shim,{withPs1:false});
await assert.rejects(resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'}),/cli\.js|修复/);
}finally{await f.cleanup();}
});
test('posix resolution is unchanged',async()=>{
const f=await layout();try{
await mkdir(f.shim,{recursive:true});await writeFile(join(f.shim,'mcode'),'#!/bin/sh');await chmod(join(f.shim,'mcode'),0o755);
const r=await resolveMcode('mcode',{env:{PATH:f.shim},home:f.official,platform:'linux'});
assert.equal(r.command,join(f.shim,'mcode'));assert.deepEqual(r.args,[]);
}finally{await f.cleanup();}
});

test('field layout: extensionless mcode + mcode.cmd + ps1 + stale sibling + releases/0.4.12 -> newest via node, never the bash script',async()=>{
const f=await layout();try{
const root=join(f.official,'.minimax-code');
await install(f.shim,{}); // provides fake powershell.exe + pwsh? (powershell only)
await writeFile(join(f.shim,'pwsh.exe'),'fake'); // pwsh preferred on the last-resort path
await mkdir(root,{recursive:true});
await writeFile(join(root,'mcode'),'#!/bin/sh'); // extensionless bash script — must never be spawned
await install(root,{version:'0.2.7',npmLayout:true,withCmd:true,withPs1:true});
const rel=join(root,'releases','0.4.12','node_modules','@minimax-ai','code');
await mkdir(rel,{recursive:true});
await writeFile(join(rel,'cli.js'),'#!/usr/bin/env node');
await writeFile(join(rel,'package.json'),pkg('0.4.12'));
// PATHEXT case must match the fixture files: real Windows filesystems are
// case-insensitive, but the POSIX runners simulating win32 are not — probing
// mcode.CMD against a lowercase mcode.cmd would miss on Linux.
const env={PATH:`${root};${f.shim}`,PATHEXT:'.cmd;.bat',SystemRoot:'C:\\Windows'};
const r=await resolveMcode('mcode',{env,home:f.official,platform:'win32'});
assert.ok(r,'resolution must succeed on the field layout');
assert.equal(r.command,process.execPath,'extensionless bash script must not be selected');
assert.equal(JSON.parse(await readFile(join(r.args[0],'..','package.json'),'utf8')).version,'0.4.12','releases layout wins over stale sibling');
}finally{await f.cleanup();}
});
test('ps1 renamed, sibling 0.2.7, releases/0.4.12 present -> releases entry (not the sibling)',async()=>{
const f=await layout();try{
const root=join(f.official,'.minimax-code');
await install(root,{version:'0.2.7',npmLayout:true,withPs1:false});
const rel=join(root,'releases','0.4.12','node_modules','@minimax-ai','code');
await mkdir(rel,{recursive:true});
await writeFile(join(rel,'cli.js'),'x');await writeFile(join(rel,'package.json'),pkg('0.4.12'));
const r=await resolveMcode('mcode',{env:winEnv(root),home:f.official,platform:'win32'});
assert.equal(JSON.parse(await readFile(join(r.args[0],'..','package.json'),'utf8')).version,'0.4.12');
}finally{await f.cleanup();}
});
test('last-resort PS hop prefers pwsh (PS7) over powershell (PS5.1 -File is broken in the field)',async()=>{
const f=await layout();try{
await install(f.shim,{});await writeFile(join(f.shim,'pwsh.exe'),'fake');
const r=await resolveMcode('mcode',{env:winEnv(f.shim),home:f.official,platform:'win32'});
assert.match(r.command,/pwsh\.exe$/i);
}finally{await f.cleanup();}
});
53 changes: 41 additions & 12 deletions plugins/hetaoBackend/mcode-dynamic-workflows/dist/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7707,7 +7707,7 @@ import { parseArgs } from "node:util";
import { resolve as resolve3, join as join4 } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir as homedir2 } from "node:os";
import { readFile as readFile2, writeFile, mkdir, open as open3, rename } from "node:fs/promises";
import { readFile as readFile3, writeFile, mkdir, open as open3, rename } from "node:fs/promises";
import { spawn as spawn3 } from "node:child_process";

// src/store.mjs
Expand Down Expand Up @@ -13939,7 +13939,7 @@ import { constants as constants2 } from "node:fs";
import { resolve as resolve2, relative, isAbsolute, sep } from "node:path";

// src/mcode-location.mjs
import { access, stat } from "node:fs/promises";
import { access, stat, readFile, readdir } from "node:fs/promises";
import { constants } from "node:fs";
import { delimiter, dirname, join as join2, resolve } from "node:path";
import { homedir } from "node:os";
Expand All @@ -13958,7 +13958,8 @@ async function executablePath(command, env = process.env, platform = process.pla
if (typeof command !== "string" || !command) return null;
const direct = /[\\/]/.test(command);
const dirs = direct ? [""] : (env.PATH ?? env.Path ?? "").split(platform === "win32" ? ";" : delimiter).filter(Boolean);
const extensions = platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
const dotted = /\.[a-z0-9]+$/i.test(command);
const extensions = platform === "win32" ? [...dotted ? [""] : [], ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] : [""];
for (const dir of dirs) for (const ext of extensions) {
const file = direct ? resolve(command + ext) : resolve(join2(dir, command + ext));
if (await fileExists(file, true, platform)) return file;
Expand All @@ -13974,15 +13975,43 @@ async function resolveMcode(command = "mcode", { env = process.env, home = homed
}
if (path) {
if (platform === "win32" && /\.(cmd|bat)$/i.test(path)) {
const root = officialRoot(home, env);
const candidates = [
join2(dirname(path), "node_modules", "@minimax-ai", "code", "cli.js"),
join2(root, "lib", "node_modules", "@minimax-ai", "code", "cli.js"),
join2(root, "node_modules", "@minimax-ai", "code", "cli.js")
];
try {
for (const entry of await readdir(join2(root, "releases"), { withFileTypes: true }))
if (entry.isDirectory()) candidates.push(join2(root, "releases", entry.name, "node_modules", "@minimax-ai", "code", "cli.js"));
} catch {
}
const versionOf = async (entry) => {
try {
const pkg = JSON.parse(await readFile(join2(entry, "..", "package.json"), "utf8"));
return String(pkg.version ?? "0.0.0").split(".").map((n) => Number.parseInt(n, 10) || 0);
} catch {
return [0, 0, 0];
}
};
const cmp = (a, b2) => a[0] - b2[0] || (a[1] ?? 0) - (b2[1] ?? 0) || (a[2] ?? 0) - (b2[2] ?? 0) || b2.length - a.length;
let best = null, bestVersion = null;
for (const entry of candidates) {
if (!await fileExists(entry)) continue;
const version3 = await versionOf(entry);
if (!best || cmp(version3, bestVersion) > 0) {
best = entry;
bestVersion = version3;
}
}
if (best) return { command: process.execPath, args: [best], source };
const launcher = join2(dirname(path), "mcode.ps1");
if (await fileExists(launcher)) {
const powershell = await executablePath("powershell.exe", env, platform) ?? await executablePath("pwsh.exe", env, platform);
const powershell = await executablePath("pwsh.exe", env, platform) ?? await executablePath("powershell.exe", env, platform);
if (!powershell) throw new Error("\u53D1\u73B0 MCode PowerShell \u542F\u52A8\u5668\uFF0C\u4F46\u627E\u4E0D\u5230 PowerShell\u3002");
return { command: powershell, args: ["-NoProfile", "-File", launcher], source };
}
const entry = join2(dirname(path), "node_modules", "@minimax-ai", "code", "cli.js");
if (!await fileExists(entry)) throw new Error(`\u53D1\u73B0 ${path}\uFF0C\u4F46\u627E\u4E0D\u5230\u53EF\u76F4\u63A5\u6267\u884C\u7684 cli.js\uFF1B\u8BF7\u4FEE\u590D\u8BE5 CLI \u5B89\u88C5\u3002`);
return { command: process.execPath, args: [entry], source };
throw new Error(`\u53D1\u73B0 ${path}\uFF0C\u4F46\u627E\u4E0D\u5230\u53EF\u76F4\u63A5\u6267\u884C\u7684 cli.js\uFF1B\u8BF7\u4FEE\u590D\u8BE5 CLI \u5B89\u88C5\u3002`);
}
return { command: path, args: [], source };
}
Expand Down Expand Up @@ -16697,7 +16726,7 @@ var REPORT_STYLES = contentStyles + reportStyles;

// src/http.mjs
import http from "node:http";
import { readFile } from "node:fs/promises";
import { readFile as readFile2 } from "node:fs/promises";

// node_modules/zod/v4/core/util.js
var util_exports = {};
Expand Down Expand Up @@ -26531,7 +26560,7 @@ async function startHTTP(engine, { port = 0, webRoot = new URL("../web/", import
const url = new URL(req.url, origin);
if (url.pathname.startsWith("/api/")) {
if (req.headers["x-workflow-client"] !== "1" || ["cross-site", "same-site"].includes(req.headers["sec-fetch-site"])) return json({ error: "\u8BF7\u4ECE\u672C\u5730 Workflow Studio \u9762\u677F\u8BBF\u95EE\u3002" }, 403);
if (req.method === "GET" && url.pathname === "/api/config") return json({ serviceProtocol: 2, features: { workflowRepair: true }, pid: process.pid, workspace: engine.options.workspace, executor: engine.options.command, defaults: engine.defaults, scheduler: engine.schedulerStatus(), mcodeAvailable: !!await resolveMcode(engine.options.command ?? "mcode"), example: await readFile(new URL("audit.js", exampleRoot), "utf8") });
if (req.method === "GET" && url.pathname === "/api/config") return json({ serviceProtocol: 2, features: { workflowRepair: true }, pid: process.pid, workspace: engine.options.workspace, executor: engine.options.command, defaults: engine.defaults, scheduler: engine.schedulerStatus(), mcodeAvailable: !!await resolveMcode(engine.options.command ?? "mcode"), example: await readFile2(new URL("audit.js", exampleRoot), "utf8") });
if (req.method === "GET" && url.pathname === "/api/templates") return json(engine.store.templates().map(({ definition, ...t }) => ({ ...t, objective: definition.metadata?.objective ?? "" })));
const template = url.pathname.match(/^\/api\/templates\/([a-f0-9-]+)$/);
if (template && req.method === "GET") {
Expand Down Expand Up @@ -26586,7 +26615,7 @@ async function startHTTP(engine, { port = 0, webRoot = new URL("../web/", import
res.writeHead(404);
return res.end();
}
const data2 = await readFile(new URL(name, webRoot));
const data2 = await readFile2(new URL(name, webRoot));
res.writeHead(200, { "Content-Type": name.endsWith(".js") ? "text/javascript; charset=utf-8" : name.endsWith(".css") ? "text/css; charset=utf-8" : "text/html; charset=utf-8", "Content-Security-Policy": `default-src 'self'; script-src 'self'; style-src 'self' 'sha256-${reportStyleHash}'; connect-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'`, "Referrer-Policy": "no-referrer", "X-Content-Type-Options": "nosniff", "Cache-Control": "no-store" });
res.end(data2);
} catch (e) {
Expand Down Expand Up @@ -27522,7 +27551,7 @@ function createWorkspaceRouter({ binary, pluginRoot, dataRoot, extraArgs = [] })

// src/main.mjs
var { values } = parseArgs({ options: { stdio: { type: "boolean" }, "stop-service": { type: "boolean" }, settings: { type: "string" }, workspace: { type: "string" }, "data-dir": { type: "string" }, port: { type: "string" }, "mcode-script": { type: "string" }, "worker-config": { type: "string" } } });
var settings = values.settings ? JSON.parse(await readFile2(resolve3(values.settings), "utf8")) : {};
var settings = values.settings ? JSON.parse(await readFile3(resolve3(values.settings), "utf8")) : {};
for (const key of Object.keys(settings)) if (!["workspace", "dataDir"].includes(key) || typeof settings[key] !== "string") throw Error("settings \u53EA\u5141\u8BB8 workspace/dataDir \u5B57\u7B26\u4E32");
if (values.port !== void 0 && (!/^\d+$/.test(values.port) || Number(values.port) > 65535)) throw Error("port \u5FC5\u987B\u662F 0\u201365535 \u7684\u6574\u6570");
var delay3 = (ms) => new Promise((r) => setTimeout(r, ms));
Expand All @@ -27537,7 +27566,7 @@ var alive = (pid) => {
};
async function readJSON(path) {
try {
return JSON.parse(await readFile2(path, "utf8"));
return JSON.parse(await readFile3(path, "utf8"));
} catch (e) {
if (e.code === "ENOENT") return null;
throw e;
Expand Down
Loading
Loading