comfyui-mcp 0.51.18 → 0.51.19
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/i18n/catalogs.test.js +209 -0
- package/dist/i18n/catalogs.test.js.map +1 -0
- package/dist/i18n/i18n.test.js +8 -0
- package/dist/i18n/i18n.test.js.map +1 -1
- package/dist/services/download-root-correspondence.js +64 -0
- package/dist/services/download-root-correspondence.js.map +1 -0
- package/dist/services/model-resolver.js +39 -1
- package/dist/services/model-resolver.js.map +1 -1
- package/locales/ar/main.json +187 -0
- package/locales/en/main.json +187 -0
- package/locales/es/main.json +187 -0
- package/locales/fa/main.json +187 -0
- package/locales/fr/main.json +187 -0
- package/locales/ja/main.json +187 -0
- package/locales/ko/main.json +187 -0
- package/locales/pt-BR/main.json +187 -0
- package/locales/ru/main.json +187 -0
- package/locales/tr/main.json +187 -0
- package/locales/zh/main.json +187 -0
- package/locales/zh-TW/main.json +187 -0
- package/package.json +3 -2
- package/scripts/i18n-build-en.mjs +816 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SHIPPED catalogs, rendered through the real runtime.
|
|
3
|
+
*
|
|
4
|
+
* i18n-check.mjs already compares the files to each other: same keys, same placeholders, right
|
|
5
|
+
* plural categories. What it cannot see is whether the running process can find and use them —
|
|
6
|
+
* a catalog can be perfect JSON in a directory `catalogFor` never looks in, under a code
|
|
7
|
+
* `resolveLocale` maps somewhere else, and the only symptom is that every language quietly
|
|
8
|
+
* renders English. That failure looks identical to "translation not started yet" and is exactly
|
|
9
|
+
* what this repo shipped before these files existed.
|
|
10
|
+
*
|
|
11
|
+
* So every assertion here goes through `trFor` rather than through the JSON, and the load-
|
|
12
|
+
* bearing one is the sentinel: a fallback string no catalog contains, which comes back
|
|
13
|
+
* unchanged if and only if the lookup missed.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { trFor, LOCALES, __resetI18nForTest } from "./index.js";
|
|
20
|
+
import { displayWidth, RULE_MAX } from "./terminal-layout.js";
|
|
21
|
+
afterEach(() => __resetI18nForTest());
|
|
22
|
+
const ROOT = join(import.meta.dirname, "..", "..");
|
|
23
|
+
const TRANSLATED = LOCALES.filter((l) => l !== "en");
|
|
24
|
+
function flatten(node, prefix = "", out = {}) {
|
|
25
|
+
for (const [k, v] of Object.entries((node ?? {}))) {
|
|
26
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
27
|
+
if (v && typeof v === "object")
|
|
28
|
+
flatten(v, key, out);
|
|
29
|
+
else if (typeof v === "string")
|
|
30
|
+
out[key] = v;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
const english = flatten(JSON.parse(readFileSync(join(ROOT, "locales", "en", "main.json"), "utf8")));
|
|
35
|
+
const KEYS = Object.keys(english).sort();
|
|
36
|
+
/** Placeholder names a value declares, e.g. `{url}` -> "url". */
|
|
37
|
+
const holes = (s) => (s.match(/\{(\w+)\}/g) ?? []).map((h) => h.slice(1, -1));
|
|
38
|
+
/**
|
|
39
|
+
* Substitution values chosen to be findable in the output. `count` stays a NUMBER because
|
|
40
|
+
* `trFor` only takes the plural path when it is one — passing a string everywhere would leave
|
|
41
|
+
* the counted strings' branch untested.
|
|
42
|
+
*/
|
|
43
|
+
function varsFor(text) {
|
|
44
|
+
const vars = {};
|
|
45
|
+
for (const name of holes(text))
|
|
46
|
+
vars[name] = name === "count" ? 7 : `«${name}»`;
|
|
47
|
+
return vars;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Status markers are NOT decoration: they are what a reader scans before reading a word, and
|
|
51
|
+
* `ready-banner.ts` states outright that every translation must reproduce them. A catalog that
|
|
52
|
+
* drops the 🟢 changes what the line asserts; one that keeps a ⚠️ the frame re-adds prints two.
|
|
53
|
+
*/
|
|
54
|
+
const MARKERS = ["🟢", "⚠️", "✓", "✅", "🔒", "⏸", "🕶️", "👁️", "ℹ️"];
|
|
55
|
+
describe("English is generated, not written", () => {
|
|
56
|
+
it("locales/en/main.json still matches what the call sites declare", () => {
|
|
57
|
+
// Same contract as the docs:gen gate — a committed generated file that no longer matches
|
|
58
|
+
// its source is a silent lie, and here the lie is "these keys are the ones we render".
|
|
59
|
+
const fresh = execFileSync(process.execPath, [join(ROOT, "scripts", "i18n-build-en.mjs"), "--print"], {
|
|
60
|
+
cwd: ROOT,
|
|
61
|
+
encoding: "utf8",
|
|
62
|
+
maxBuffer: 1 << 26,
|
|
63
|
+
});
|
|
64
|
+
expect(fresh).toBe(readFileSync(join(ROOT, "locales", "en", "main.json"), "utf8"));
|
|
65
|
+
});
|
|
66
|
+
it("declares a catalog worth translating", () => {
|
|
67
|
+
expect(KEYS.length).toBeGreaterThan(100);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe.each(TRANSLATED)("%s renders through the real runtime", (locale) => {
|
|
71
|
+
it("answers EVERY key — a sentinel fallback never survives", () => {
|
|
72
|
+
// The whole point. `«no catalog»` appears in no file, so getting it back means the lookup
|
|
73
|
+
// missed: wrong directory, wrong locale code, a nesting level `flatten` did not descend.
|
|
74
|
+
const missed = KEYS.filter((key) => trFor(locale, key, "«no catalog»") === "«no catalog»");
|
|
75
|
+
expect(missed).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
it("never renders a key as its own name", () => {
|
|
78
|
+
const leaked = KEYS.filter((key) => {
|
|
79
|
+
const out = trFor(locale, key, english[key], varsFor(english[key]));
|
|
80
|
+
return out === key || out.includes(key);
|
|
81
|
+
});
|
|
82
|
+
expect(leaked).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
it("substitutes every placeholder and leaves none behind", () => {
|
|
85
|
+
const broken = [];
|
|
86
|
+
for (const key of KEYS) {
|
|
87
|
+
const vars = varsFor(english[key]);
|
|
88
|
+
const out = trFor(locale, key, english[key], vars);
|
|
89
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
90
|
+
if (!out.includes(String(value)))
|
|
91
|
+
broken.push(`${key}: {${name}} did not substitute`);
|
|
92
|
+
}
|
|
93
|
+
// A `{leftover}` means the translation renamed or invented a placeholder: the runtime
|
|
94
|
+
// leaves an unknown hole verbatim, so the reader sees braces instead of the answer.
|
|
95
|
+
const leftover = out.match(/\{\w+\}/g);
|
|
96
|
+
if (leftover)
|
|
97
|
+
broken.push(`${key}: unresolved ${leftover.join(", ")}`);
|
|
98
|
+
}
|
|
99
|
+
expect(broken).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
it("renders no empty value", () => {
|
|
102
|
+
const empty = KEYS.filter((key) => !trFor(locale, key, english[key], varsFor(english[key])).trim());
|
|
103
|
+
expect(empty).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
it("keeps the status marker the English line opens with", () => {
|
|
106
|
+
const dropped = [];
|
|
107
|
+
for (const key of KEYS) {
|
|
108
|
+
const marker = MARKERS.find((m) => english[key].startsWith(m));
|
|
109
|
+
if (!marker)
|
|
110
|
+
continue;
|
|
111
|
+
const out = trFor(locale, key, english[key], varsFor(english[key]));
|
|
112
|
+
if (!out.startsWith(marker))
|
|
113
|
+
dropped.push(`${key}: expected to start with ${marker}`);
|
|
114
|
+
}
|
|
115
|
+
expect(dropped).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
it("keeps every backticked command and every ENV_VAR verbatim", () => {
|
|
118
|
+
// These are things the reader TYPES. A translated `codex login` or a localised
|
|
119
|
+
// OPENROUTER_API_KEY is an instruction that cannot work, and no shape check sees it.
|
|
120
|
+
const lost = [];
|
|
121
|
+
for (const key of KEYS) {
|
|
122
|
+
const out = trFor(locale, key, english[key], varsFor(english[key]));
|
|
123
|
+
for (const cmd of english[key].match(/`[^`]+`/g) ?? []) {
|
|
124
|
+
if (!out.includes(cmd))
|
|
125
|
+
lost.push(`${key}: lost ${cmd}`);
|
|
126
|
+
}
|
|
127
|
+
for (const env of english[key].match(/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+){2,}\b/g) ?? []) {
|
|
128
|
+
if (!out.includes(env))
|
|
129
|
+
lost.push(`${key}: lost ${env}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
expect(lost).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* Strings that land INSIDE one of the two `════` boxes the CLI draws.
|
|
137
|
+
*
|
|
138
|
+
* Enumerated rather than matched on a `cli.connect_`/`cli.tunnel_` prefix: `connect_url_invalid`
|
|
139
|
+
* and `cannot_start` share those prefixes and are free-flowing fatal lines with no box around
|
|
140
|
+
* them, so a keyword rule would hold them to a width they have no reason to respect — and the
|
|
141
|
+
* repo has been bitten before by a guard whose over-broad direction is invisible in its own
|
|
142
|
+
* output. A key added to a banner has to be added here too; the render in the PR is the
|
|
143
|
+
* backstop that notices if it is not.
|
|
144
|
+
*/
|
|
145
|
+
const BOXED_KEYS = [
|
|
146
|
+
"connect_title",
|
|
147
|
+
"connect_bridge_secure",
|
|
148
|
+
"connect_label_bridge",
|
|
149
|
+
"connect_label_driving",
|
|
150
|
+
"connect_label_secure",
|
|
151
|
+
"connect_secure_note",
|
|
152
|
+
"connect_next_steps",
|
|
153
|
+
"connect_step_open_comfyui",
|
|
154
|
+
"connect_step_enable_external_orchestrator",
|
|
155
|
+
"connect_step_click_connect",
|
|
156
|
+
"connect_quiet_note",
|
|
157
|
+
"tunnel_title",
|
|
158
|
+
"tunnel_label_public_url",
|
|
159
|
+
"tunnel_label_token",
|
|
160
|
+
"tunnel_desktop_connector_path",
|
|
161
|
+
"tunnel_field_header",
|
|
162
|
+
"tunnel_field_name",
|
|
163
|
+
"tunnel_field_url",
|
|
164
|
+
"tunnel_header_alt",
|
|
165
|
+
"tunnel_headless_snippet",
|
|
166
|
+
"tunnel_keep_open",
|
|
167
|
+
];
|
|
168
|
+
describe("box-drawn banners survive translation", () => {
|
|
169
|
+
it.each(LOCALES)("%s keeps every boxed line inside the rule", (locale) => {
|
|
170
|
+
// CJK is TWO cells per character and the box caps its rule at RULE_MAX, so a sentence that
|
|
171
|
+
// is a comfortable 70 characters in English can be a 140-cell line that walks straight out
|
|
172
|
+
// of the frame. This is a NECESSARY condition, not a sufficient one — every boxed line also
|
|
173
|
+
// carries a prefix (a label column, a step number) that this cannot know — which is why the
|
|
174
|
+
// PR pastes real renders. What it does catch, cheaply and forever, is the gross case.
|
|
175
|
+
const budget = RULE_MAX - 1; // every free-standing banner line is written as ` ${text}`
|
|
176
|
+
const tooWide = [];
|
|
177
|
+
for (const key of BOXED_KEYS) {
|
|
178
|
+
const full = `cli.${key}`;
|
|
179
|
+
expect(english[full], `${full} is not in the English catalog — was a banner key renamed?`).toBeTypeOf("string");
|
|
180
|
+
const rendered = trFor(locale, full, english[full], varsFor(english[full]));
|
|
181
|
+
for (const line of rendered.split("\n")) {
|
|
182
|
+
const w = displayWidth(line);
|
|
183
|
+
if (w > budget)
|
|
184
|
+
tooWide.push(`${full}: ${w} cells > ${budget} — ${line}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
expect(tooWide).toEqual([]);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe("the catalogs are actually being read", () => {
|
|
191
|
+
it("translates every key that is prose rather than an acronym", () => {
|
|
192
|
+
// Guards the case a sentinel cannot: a file that loads but is a copy of English. The
|
|
193
|
+
// exemption is i18n-check's own rule — a value with no run of four lowercase letters is
|
|
194
|
+
// "API", "URL", "PANEL / COMFYUI", which are the same word in every language and would
|
|
195
|
+
// be WRONG to change. Using the same rule here keeps the two gates from disagreeing.
|
|
196
|
+
const isProse = (s) => /[a-z]{4}/.test(s);
|
|
197
|
+
for (const locale of TRANSLATED) {
|
|
198
|
+
const same = KEYS.filter((key) => isProse(english[key]) && trFor(locale, key, english[key]) === english[key]);
|
|
199
|
+
expect(`${locale}: ${same.join(", ") || "none"}`).toBe(`${locale}: none`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
it("resolves the regional codes the way the files are named", () => {
|
|
203
|
+
// `pt-BR` and `zh-TW` are the two codes where a directory name and a resolved locale could
|
|
204
|
+
// disagree; if they did, those two languages would silently render English forever.
|
|
205
|
+
expect(trFor("pt_BR.UTF-8", "console.credentials.save", "Save")).not.toBe("Save");
|
|
206
|
+
expect(trFor("zh-HK", "console.credentials.save", "Save")).not.toBe("Save");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
//# sourceMappingURL=catalogs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalogs.test.js","sourceRoot":"","sources":["../../src/i18n/catalogs.test.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n/i18n.test.js
CHANGED
|
@@ -43,6 +43,14 @@ describe("locale resolution", () => {
|
|
|
43
43
|
it("--lang beats the environment, and explicit env beats the POSIX chain", () => {
|
|
44
44
|
const prev = { ...process.env };
|
|
45
45
|
try {
|
|
46
|
+
// Own the WHOLE chain, not just the link under test. The suite now pins
|
|
47
|
+
// COMFYUI_MCP_LANG=en (see helpers/isolated-test-home.ts), and this case asserts that
|
|
48
|
+
// LANG is consulted — which is only observable when the higher-precedence variables are
|
|
49
|
+
// absent. Leaving them to the ambient environment made the test assert "en beats fr" on
|
|
50
|
+
// a pinned machine and "ja beats fr" for anyone with LANG=ja_JP, i.e. it measured the
|
|
51
|
+
// developer rather than the code.
|
|
52
|
+
for (const k of ["COMFYUI_MCP_LANG", "LC_ALL", "LC_MESSAGES"])
|
|
53
|
+
delete process.env[k];
|
|
46
54
|
process.env.LANG = "fr_FR.UTF-8";
|
|
47
55
|
expect(processLocale(["node", "cli", "--lang", "ko"])).toBe("ko");
|
|
48
56
|
expect(processLocale(["node", "cli", "--lang=ja"])).toBe("ja");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.test.js","sourceRoot":"","sources":["../../src/i18n/i18n.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"i18n.test.js","sourceRoot":"","sources":["../../src/i18n/i18n.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1371 — a model was written into a DIFFERENT local ComfyUI installation than the one
|
|
3
|
+
* serving the session.
|
|
4
|
+
*
|
|
5
|
+
* Connected to ComfyUI on `:8190` (`…\ComfyUI_H3`), the download streamed into
|
|
6
|
+
* `…\ComfyUI\models` — a stale `COMFYUI_PATH` from another install — while visibility
|
|
7
|
+
* was checked against the connected server.
|
|
8
|
+
*
|
|
9
|
+
* ## Why the existing guard did not catch it
|
|
10
|
+
*
|
|
11
|
+
* `model-resolver.ts` has refused divergent destinations since v0.49.4, and the reporter
|
|
12
|
+
* was on 0.50.107 — newer — so the guard was present and did not fire. It proves
|
|
13
|
+
* divergence from CONTENT: files on disk the running server does not list. With a sparse
|
|
14
|
+
* or empty destination category (`vae`, here) there is nothing to compare.
|
|
15
|
+
*
|
|
16
|
+
* ## The evidence used, and one that was REFUTED first
|
|
17
|
+
*
|
|
18
|
+
* A first attempt refused when the destination fell outside the install root the server
|
|
19
|
+
* names for itself. Review knocked that down, correctly: being outside the install root
|
|
20
|
+
* does not mean the server cannot read it. `extra_model_paths.yaml` registers roots
|
|
21
|
+
* elsewhere by design, `<install>/models` is routinely a junction to another drive, and
|
|
22
|
+
* a container-mounted path and its host destination describe one directory through two
|
|
23
|
+
* unrelated strings. #1474 — fixed the same day — is a Comfy Desktop install serving
|
|
24
|
+
* models from a SHARED root outside its install directory; that guard would have refused
|
|
25
|
+
* it outright.
|
|
26
|
+
*
|
|
27
|
+
* The right question is not "is this inside the install" but "does the connected server
|
|
28
|
+
* read this tree", and `isUnderLiveModelRoots` already answers exactly that: it checks
|
|
29
|
+
* the live primary models root AND live-registered extra roots, canonicalizes junctions
|
|
30
|
+
* and symlinks through `realpath`, is category-scoped, and returns `undefined` for
|
|
31
|
+
* UNKNOWN rather than folding it into "no".
|
|
32
|
+
*
|
|
33
|
+
* So this refuses on `inRoots === false` — demonstrably not read — and never on
|
|
34
|
+
* `undefined`. That is the distinction the whole file exists to hold: the surrounding
|
|
35
|
+
* code's own note records that every time "the server did not vouch for it" was treated
|
|
36
|
+
* as "the server cannot read it", it produced another false refusal.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* The refusal for a destination the connected server demonstrably does not read.
|
|
40
|
+
*
|
|
41
|
+
* `inRoots` comes from `isUnderLiveModelRoots`: `false` means demonstrably outside every
|
|
42
|
+
* tree the live server reads; `undefined` means only local configuration could answer,
|
|
43
|
+
* which proves nothing and must NOT refuse.
|
|
44
|
+
*/
|
|
45
|
+
export function divergentInstallRefusal(opts) {
|
|
46
|
+
const { targetDir, inRoots, liveRoot, serverUrl } = opts;
|
|
47
|
+
if (inRoots !== false)
|
|
48
|
+
return null; // true = fine; undefined = unknown, never refuse
|
|
49
|
+
if (!targetDir)
|
|
50
|
+
return null;
|
|
51
|
+
const where = liveRoot ? ` It reads models from "${liveRoot}" (and any extra roots it has registered).` : "";
|
|
52
|
+
return (`NOT downloaded: the connected ComfyUI${serverUrl ? ` (${serverUrl})` : ""} does not ` +
|
|
53
|
+
`read from "${targetDir}", so a model placed there would never be visible to it.` +
|
|
54
|
+
`${where} This destination came from LOCAL CONFIGURATION — COMFYUI_PATH or the saved ` +
|
|
55
|
+
`default workspace — not from the running server, and a COMFYUI_PATH left over from a ` +
|
|
56
|
+
`different installation is the usual cause (#1371). Nothing was written.\n\n` +
|
|
57
|
+
`This is a demonstrated mismatch, not an unverified one: the destination was checked ` +
|
|
58
|
+
`against the roots the live server actually reads, including any registered through ` +
|
|
59
|
+
`extra_model_paths.yaml, and with junctions and symlinks resolved. Point COMFYUI_PATH ` +
|
|
60
|
+
`at the ComfyUI that is actually running, or launch that server with an absolute ` +
|
|
61
|
+
`--base-directory, then retry. If you MEANT another install, download it there — ` +
|
|
62
|
+
`writing here would report success for a model the connected server can never load.`);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=download-root-correspondence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-root-correspondence.js","sourceRoot":"","sources":["../../src/services/download-root-correspondence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAMvC;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACzD,IAAI,OAAO,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC,CAAC,iDAAiD;IACrF,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,0BAA0B,QAAQ,4CAA4C,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7G,OAAO,CACL,wCAAwC,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,YAAY;QACtF,cAAc,SAAS,0DAA0D;QACjF,GAAG,KAAK,8EAA8E;QACtF,uFAAuF;QACvF,6EAA6E;QAC7E,sFAAsF;QACtF,qFAAqF;QACrF,uFAAuF;QACvF,kFAAkF;QAClF,kFAAkF;QAClF,oFAAoF,CACrF,CAAC;AACJ,CAAC"}
|
|
@@ -6,13 +6,14 @@ import { dirname, join, basename, normalize, resolve, relative, sep, isAbsolute,
|
|
|
6
6
|
import { config, getComfyUIBaseUrl, isRemoteMode } from "../config.js";
|
|
7
7
|
import { getClient, getLogs, getSystemStats, comfyApiFetch } from "../comfyui/client.js";
|
|
8
8
|
import { getExtraModelRoots, getLiveExtraModelRoots } from "./extra-paths.js";
|
|
9
|
-
import { resolveEffectiveComfyUIBase, resolveLiveServerRoot } from "./workspace-env.js";
|
|
9
|
+
import { resolveEffectiveComfyUIBase, resolveLiveServerRoot, } from "./workspace-env.js";
|
|
10
10
|
import { installModelViaManager } from "./node-management.js";
|
|
11
11
|
import { ModelError, ValidationError, unreachableHostMessage } from "../utils/errors.js";
|
|
12
12
|
import { logger } from "../utils/logger.js";
|
|
13
13
|
import { downloadWithCache, probeRemoteModelPayload } from "./download-cache.js";
|
|
14
14
|
import { reportDownloadProgress } from "./download-progress.js";
|
|
15
15
|
import { modelNotFoundMessage } from "./model-root-scope.js";
|
|
16
|
+
import { divergentInstallRefusal } from "./download-root-correspondence.js";
|
|
16
17
|
import { resolveModelsDir, resolveModelsDirWithBases, parseModelsDirFromArgv, hasUnresolvableRelativeModelDirFlag, isLiveAuthoritativeModelsDir, } from "./output-dir.js";
|
|
17
18
|
import { applyDownloadAuth, redactUrlForLogs, } from "./download-auth.js";
|
|
18
19
|
export const MODEL_SUBDIRS = [
|
|
@@ -1787,6 +1788,12 @@ liveSnapshot = { reachable: false }) {
|
|
|
1787
1788
|
cursor = parent;
|
|
1788
1789
|
}
|
|
1789
1790
|
}
|
|
1791
|
+
/** The models CATEGORY a target subfolder belongs to — its first segment. Live extra
|
|
1792
|
+
* roots are category-scoped, so a `checkpoints` root must not vouch for a LoRA. */
|
|
1793
|
+
function normalizedSubfolderFor(targetSubfolder) {
|
|
1794
|
+
const first = (targetSubfolder ?? "").trim().replace(/\\/g, "/").split("/")[0];
|
|
1795
|
+
return first || undefined;
|
|
1796
|
+
}
|
|
1790
1797
|
/**
|
|
1791
1798
|
* The SINGLE source of truth for a model download's final on-disk destination,
|
|
1792
1799
|
* shared by downloadModel (the writer) AND the background job registry (which
|
|
@@ -1816,6 +1823,37 @@ export async function resolveDownloadTarget(url, targetSubfolder, filename) {
|
|
|
1816
1823
|
if (!resolve(targetPath).startsWith(resolve(targetDir) + sep)) {
|
|
1817
1824
|
throw new ModelError("Refusing to write outside the target model directory.", { filename: rawFilename });
|
|
1818
1825
|
}
|
|
1826
|
+
// #1371 — a stale COMFYUI_PATH wrote a model into a DIFFERENT local install than the
|
|
1827
|
+
// one serving the session. The destination is rooted at the server's own models dir
|
|
1828
|
+
// when it can be, but falls back to <COMFYUI_PATH>/models when the server was not
|
|
1829
|
+
// launched with --base-directory — and nothing then checked that the fallback belongs
|
|
1830
|
+
// to THAT server.
|
|
1831
|
+
//
|
|
1832
|
+
// A server can still name its install root through its main.py argv. When it does and
|
|
1833
|
+
// the destination is outside it, the installs are PROVABLY different. Positive
|
|
1834
|
+
// evidence, so this does not reintroduce the false-refusal class the content check's
|
|
1835
|
+
// own note warns about — and it is skipped entirely when the destination came from
|
|
1836
|
+
// the live server (nothing to disagree with) or when no root can be derived.
|
|
1837
|
+
if (!liveRootAtResolve) {
|
|
1838
|
+
const membership = await isUnderLiveModelRoots(targetDir, normalizedSubfolderFor(targetSubfolder));
|
|
1839
|
+
const refusal = divergentInstallRefusal({
|
|
1840
|
+
targetDir,
|
|
1841
|
+
inRoots: membership.inRoots,
|
|
1842
|
+
liveRoot: membership.liveRoot,
|
|
1843
|
+
// Defensive: decoration on a refusal, and an existing suite mocks config.js
|
|
1844
|
+
// without this export — a message detail must never fail a download.
|
|
1845
|
+
serverUrl: (() => {
|
|
1846
|
+
try {
|
|
1847
|
+
return getComfyUIBaseUrl();
|
|
1848
|
+
}
|
|
1849
|
+
catch {
|
|
1850
|
+
return undefined;
|
|
1851
|
+
}
|
|
1852
|
+
})(),
|
|
1853
|
+
});
|
|
1854
|
+
if (refusal)
|
|
1855
|
+
throw new ModelError(refusal, { path: targetDir });
|
|
1856
|
+
}
|
|
1819
1857
|
return { targetDir, filename: resolvedFilename, targetPath, liveRootAtResolve };
|
|
1820
1858
|
}
|
|
1821
1859
|
/**
|