mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-03-25 10:53:28 +08:00
Compare commits
52 Commits
5fcf9f5ec6
...
3c9d8c6039
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c9d8c6039 | ||
|
|
c8dce94c03 | ||
|
|
06496d07b3 | ||
|
|
a97f98c9cc | ||
|
|
8d0406f74f | ||
|
|
c64d14701d | ||
|
|
00332ae444 | ||
|
|
e8deb3d8fe | ||
|
|
8b234c99cf | ||
|
|
1f986d9c45 | ||
|
|
bacb8fb3cd | ||
|
|
e4a90089ab | ||
|
|
674b9f3705 | ||
|
|
4941fb8aa0 | ||
|
|
183af0dfa5 | ||
|
|
45ac5429f8 | ||
|
|
c771977a95 | ||
|
|
668d7bbb2c | ||
|
|
926cfabb58 | ||
|
|
a9a8d05115 | ||
|
|
e368f4366a | ||
|
|
dc5bddbc17 | ||
|
|
358a480408 | ||
|
|
c96fdb3c7a | ||
|
|
c090abcc02 | ||
|
|
1ff02be35f | ||
|
|
10fbfb88f7 | ||
|
|
9753df72ed | ||
|
|
095cc3f792 | ||
|
|
656171037b | ||
|
|
7ac10f9442 | ||
|
|
3925ba27b4 | ||
|
|
44ba79aa31 | ||
|
|
14d0e31268 | ||
|
|
033acffad1 | ||
|
|
d29ff808a5 | ||
|
|
dc9b6d655b | ||
|
|
d340c85013 | ||
|
|
e328353664 | ||
|
|
02785af8fd | ||
|
|
736ae5d63e | ||
|
|
e1eeb617d2 | ||
|
|
23b6c7f0de | ||
|
|
997f97e1fc | ||
|
|
ff335ff1a0 | ||
|
|
cb3036ef81 | ||
|
|
f762906188 | ||
|
|
dde7920f8c | ||
|
|
1a0d24110a | ||
|
|
e79f6c4471 | ||
|
|
a8a7024a84 | ||
|
|
dfd953b2ae |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10136
github-stats.json
10136
github-stats.json
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,7 @@ import manager_downloader
|
||||
from node_package import InstalledNodePackage
|
||||
|
||||
|
||||
version_code = [3, 37, 1]
|
||||
version_code = [3, 37, 2]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
|
||||
@ -2533,6 +2533,7 @@ def update_to_stable_comfyui(repo_path):
|
||||
else:
|
||||
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
||||
repo.git.checkout(latest_tag)
|
||||
execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False)
|
||||
return 'updated', latest_tag
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
63
js/common.js
63
js/common.js
@ -347,6 +347,55 @@ export function md5(inputString) {
|
||||
return rh(a)+rh(b)+rh(c)+rh(d);
|
||||
}
|
||||
|
||||
const levenArray = [];
|
||||
const levenCodeCache = [];
|
||||
export const leven = (first, second) => {
|
||||
if (first === second) {
|
||||
return 0;
|
||||
}
|
||||
const swap = first;
|
||||
if (first.length > second.length) {
|
||||
first = second;
|
||||
second = swap;
|
||||
}
|
||||
let firstLength = first.length;
|
||||
let secondLength = second.length;
|
||||
while (firstLength > 0 && (first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength))) {
|
||||
firstLength--;
|
||||
secondLength--;
|
||||
}
|
||||
let start = 0;
|
||||
while (start < firstLength && (first.charCodeAt(start) === second.charCodeAt(start))) {
|
||||
start++;
|
||||
}
|
||||
firstLength -= start;
|
||||
secondLength -= start;
|
||||
if (firstLength === 0) {
|
||||
return secondLength;
|
||||
}
|
||||
let bCharacterCode;
|
||||
let result;
|
||||
let temporary;
|
||||
let temporary2;
|
||||
let index = 0;
|
||||
let index2 = 0;
|
||||
while (index < firstLength) {
|
||||
levenCodeCache[index] = first.charCodeAt(start + index);
|
||||
levenArray[index] = ++index;
|
||||
}
|
||||
while (index2 < secondLength) {
|
||||
bCharacterCode = second.charCodeAt(start + index2);
|
||||
temporary = index2++;
|
||||
result = index2;
|
||||
for (index = 0; index < firstLength; index++) {
|
||||
temporary2 = bCharacterCode === levenCodeCache[index] ? temporary : temporary + 1;
|
||||
temporary = levenArray[index];
|
||||
result = levenArray[index] = temporary > result ? (temporary2 > result ? result + 1 : temporary2) : (temporary2 > temporary ? temporary + 1 : temporary2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fetchData(route, options) {
|
||||
let err;
|
||||
const res = await api.fetchApi(route, options).catch(e => {
|
||||
@ -595,12 +644,10 @@ export function showPopover(target, text, className, options) {
|
||||
}
|
||||
|
||||
let $tooltip;
|
||||
export function hideTooltip(target) {
|
||||
export function hideTooltip() {
|
||||
if ($tooltip) {
|
||||
$tooltip.style.display = "none";
|
||||
$tooltip.innerHTML = "";
|
||||
$tooltip.style.top = "0px";
|
||||
$tooltip.style.left = "0px";
|
||||
$tooltip.remove();
|
||||
$tooltip = null;
|
||||
}
|
||||
}
|
||||
export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {}) {
|
||||
@ -639,11 +686,7 @@ function initTooltip () {
|
||||
}
|
||||
};
|
||||
const mouseleaveHandler = (e) => {
|
||||
const target = e.target;
|
||||
const text = target.getAttribute('tooltip');
|
||||
if (text) {
|
||||
hideTooltip(target);
|
||||
}
|
||||
hideTooltip();
|
||||
};
|
||||
document.body.removeEventListener('mouseenter', mouseenterHandler, true);
|
||||
document.body.removeEventListener('mouseleave', mouseleaveHandler, true);
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
|
||||
sanitizeHTML, infoToast, showTerminal, setNeedRestart,
|
||||
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
|
||||
showPopover, hidePopover
|
||||
showPopover, hidePopover, leven
|
||||
} from "./common.js";
|
||||
|
||||
// https://cenfun.github.io/turbogrid/api.html
|
||||
@ -418,11 +418,7 @@ export class CustomNodesManager {
|
||||
|
||||
".cn-manager-keywords": {
|
||||
input: (e) => {
|
||||
const keywords = `${e.target.value}`.trim();
|
||||
if (keywords !== this.keywords) {
|
||||
this.keywords = keywords;
|
||||
this.updateGrid();
|
||||
}
|
||||
this.onKeywordsChange(e);
|
||||
},
|
||||
focus: (e) => e.target.select()
|
||||
},
|
||||
@ -539,7 +535,7 @@ export class CustomNodesManager {
|
||||
|
||||
this.addHighlight(d.rowItem);
|
||||
|
||||
if (d.columnItem.id === "nodes") {
|
||||
if (d.columnItem && d.columnItem.id === "nodes") {
|
||||
this.showNodes(d);
|
||||
return;
|
||||
}
|
||||
@ -596,6 +592,14 @@ export class CustomNodesManager {
|
||||
return autoHeightColumns.includes(columnItem.id)
|
||||
},
|
||||
|
||||
rowFilteredSort: () => {
|
||||
if (this.keywords) {
|
||||
return {
|
||||
id: 'sort_score'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// updateGrid handler for filter and keywords
|
||||
rowFilter: (rowItem) => {
|
||||
|
||||
@ -612,12 +616,109 @@ export class CustomNodesManager {
|
||||
}
|
||||
}
|
||||
|
||||
// calculate sort score
|
||||
if (shouldShown && this.keywords) {
|
||||
rowItem.sort_score = this.calculateSortScore(rowItem, searchableColumns);
|
||||
}
|
||||
|
||||
return shouldShown;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onKeywordsChange(e) {
|
||||
this.grid.showLoading();
|
||||
// debounce for performance
|
||||
clearTimeout(this.timeKeywords);
|
||||
this.timeKeywords = setTimeout(() => {
|
||||
this.grid.hideLoading();
|
||||
const keywords = `${e.target.value}`.trim();
|
||||
if (keywords !== this.keywords) {
|
||||
this.keywords = keywords;
|
||||
if (keywords) {
|
||||
this.grid.removeSortColumn();
|
||||
} else {
|
||||
this.grid.sortRows("id", {
|
||||
sortAsc: true
|
||||
});
|
||||
}
|
||||
this.updateGrid();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
calculateSortScore(rowItem, searchableColumns) {
|
||||
const keywords = this.keywords.split(/\s+/g).filter((s) => s);
|
||||
const lowerKeywords = keywords.map(k => k.toLowerCase());
|
||||
const matchedList = searchableColumns.map(id => {
|
||||
const { highlightKey, textKey } = this.grid.options.highlightKeywords;
|
||||
const highlight = rowItem[`${highlightKey}${id}`];
|
||||
if (!highlight) {
|
||||
return;
|
||||
}
|
||||
const text = `${rowItem[`${textKey}${id}`] || rowItem[id]}`;
|
||||
const lowerText = text.toLowerCase();
|
||||
const matchedItems = keywords.map((key, i) => {
|
||||
// multiple matched
|
||||
const lowerKey = lowerKeywords[i];
|
||||
const len = lowerKey.length;
|
||||
const matches = [];
|
||||
let index = lowerText.indexOf(lowerKey);
|
||||
while (index !== -1) {
|
||||
matches.push(index);
|
||||
index = lowerText.indexOf(lowerKey, index + len);
|
||||
}
|
||||
if (!matches.length) {
|
||||
return
|
||||
}
|
||||
const distances = matches.map(start => {
|
||||
const end = start + len
|
||||
const str = text.slice(start, end);
|
||||
let distance = leven(key, str);
|
||||
const prev = text.slice(start - 1, start);
|
||||
if (prev) {
|
||||
if (/[A-Za-z]/.test(prev)) {
|
||||
distance += 1;
|
||||
} else if (/[0-9]/.test(prev)) {
|
||||
distance += 0.8;
|
||||
}
|
||||
}
|
||||
const next = text.slice(end, end + 1);
|
||||
if (next) {
|
||||
if (/[A-Za-z]/.test(next)) {
|
||||
distance += 0.8;
|
||||
} else if (/[0-9]/.test(next)) {
|
||||
distance += 0.5;
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
});
|
||||
// console.log(rowItem.title, distances)
|
||||
return {
|
||||
// min
|
||||
distance: Math.min.apply(null, distances)
|
||||
}
|
||||
}).filter(it => it);
|
||||
if (matchedItems.length < keywords.length) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
// avg
|
||||
distance: matchedItems.map(it => it.distance).reduce((p, v) => p + v, 0) / matchedItems.length
|
||||
}
|
||||
}).filter(it => it);
|
||||
// by distance
|
||||
let distance = Math.min.apply(null, matchedList.map(it => it.distance));
|
||||
// by matched count
|
||||
distance += 1 - matchedList.length / searchableColumns.length;
|
||||
// by stars
|
||||
const stars = TG.Util.toNum(rowItem.stars);
|
||||
distance += 1 - stars / 10000;
|
||||
// score
|
||||
return 1 / distance;
|
||||
}
|
||||
|
||||
hasAlternatives() {
|
||||
return this.filter === ShowMode.ALTERNATIVES
|
||||
}
|
||||
@ -757,6 +858,7 @@ export class CustomNodesManager {
|
||||
id: "nodes",
|
||||
name: "Nodes",
|
||||
width: 100,
|
||||
sortAsc: false,
|
||||
formatter: (v, rowItem, columnItem) => {
|
||||
if (!rowItem.nodes) {
|
||||
return '';
|
||||
@ -797,6 +899,7 @@ export class CustomNodesManager {
|
||||
id: 'stars',
|
||||
name: '★',
|
||||
align: 'center',
|
||||
sortAsc: false,
|
||||
classMap: "cn-pack-stars",
|
||||
formatter: (stars) => {
|
||||
if (stars < 0) {
|
||||
@ -812,6 +915,7 @@ export class CustomNodesManager {
|
||||
name: 'Last Update',
|
||||
align: 'center',
|
||||
type: 'date',
|
||||
sortAsc: false,
|
||||
width: 100,
|
||||
classMap: "cn-pack-last-update",
|
||||
formatter: (last_update) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -169,6 +169,16 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A fork of KJNodes for ComfyUI.\nVarious quality of life -nodes for ComfyUI, mostly just visual stuff to improve usability"
|
||||
},
|
||||
{
|
||||
"author": "huixingyun",
|
||||
"title": "ComfyUI-SoundFlow",
|
||||
"reference": "https://github.com/huixingyun/ComfyUI-SoundFlow",
|
||||
"files": [
|
||||
"https://github.com/huixingyun/ComfyUI-SoundFlow"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "forked from https://github.com/fredconex/ComfyUI-SoundFlow (removed)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,5 +1,155 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "PozzettiAndrea",
|
||||
"title": "ComfyUI-CameraAnalysis [REMOVED]",
|
||||
"reference": "https://github.com/PozzettiAndrea/ComfyUI-CameraAnalysis",
|
||||
"files": [
|
||||
"https://github.com/PozzettiAndrea/ComfyUI-CameraAnalysis"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Extracts camera intrinsic parameters from image EXIF data."
|
||||
},
|
||||
{
|
||||
"author": "fuzr0dah",
|
||||
"title": "comfyui-sceneassembly [REMOVED]",
|
||||
"reference": "https://github.com/fuzr0dah/comfyui-sceneassembly",
|
||||
"files": [
|
||||
"https://github.com/fuzr0dah/comfyui-sceneassembly"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A bunch of nodes I created that I also find useful."
|
||||
},
|
||||
{
|
||||
"author": "rslosch",
|
||||
"title": "ComfyUI-EZ_Prompts [REMOVED]",
|
||||
"reference": "https://github.com/rslosch/ComfyUI-EZ_Prompts",
|
||||
"files": [
|
||||
"https://github.com/rslosch/ComfyUI-EZ_Prompts"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI custom node extension that provides easy-to-use prompt templates and wildcards for AI image generation."
|
||||
},
|
||||
{
|
||||
"author": "hvppycoding",
|
||||
"title": "hvppyflow [REMOVED]",
|
||||
"reference": "https://github.com/hvppycoding/hvppyflow",
|
||||
"files": [
|
||||
"https://github.com/hvppycoding/hvppyflow"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI nodes for Automated Workflow"
|
||||
},
|
||||
{
|
||||
"author": "cedarconnor",
|
||||
"title": "ComfyUI-GEN3C-Gsplat [REMOVED]",
|
||||
"reference": "https://github.com/cedarconnor/ComfyUI-GEN3C-Gsplat",
|
||||
"files": [
|
||||
"https://github.com/cedarconnor/ComfyUI-GEN3C-Gsplat"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom ComfyUI node pack that bridges Cosmos/GEN3C video generation with in-graph Gaussian Splat (3DGS) training. It adds camera/trajectory tooling, dataset exporters, and two training backends (Nerfstudio CLI wrapper and an in-process gsplat optimizer) so artists can go from prompt to splat entirely inside ComfyUI.\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "dowa-git",
|
||||
"title": "comfyui-dowa [REMOVED]",
|
||||
"reference": "https://github.com/dowa-git/comfyui-dowa",
|
||||
"files": [
|
||||
"https://github.com/dowa-git/comfyui-dowa"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Professional navigation bar widget for ComfyUI with JWT-based user authentication, workflow templates, and team collaboration features in a purple gradient design."
|
||||
},
|
||||
{
|
||||
"author": "Fablestarexpanse",
|
||||
"title": "Timer-Node-Comfyui [REMOVED]",
|
||||
"reference": "https://github.com/Fablestarexpanse/Timer-Node-Comfyui",
|
||||
"files": [
|
||||
"https://github.com/Fablestarexpanse/Timer-Node-Comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom ComfyUI node that displays live processing time in a red digital countdown clock format, perfect for monitoring image generation times and tracking performance between workflow nodes."
|
||||
},
|
||||
{
|
||||
"author": "cedarconnor",
|
||||
"title": "ComfyUI-OmniX [REMOVED]",
|
||||
"reference": "https://github.com/cedarconnor/ComfyUI-OmniX",
|
||||
"files": [
|
||||
"https://github.com/cedarconnor/ComfyUI-OmniX"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Extract comprehensive scene properties from 360-degree equirectangular panoramas, including depth, normals, and PBR materials, using OmniX adapters with Flux."
|
||||
},
|
||||
{
|
||||
"author": "cedarconnor",
|
||||
"title": "ComfyUI-DiT360 [REMOVED]",
|
||||
"reference": "https://github.com/cedarconnor/ComfyUI-DiT360",
|
||||
"files": [
|
||||
"https://github.com/cedarconnor/ComfyUI-DiT360"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Generate high-fidelity 360-degree panoramic images using the DiT360 diffusion transformer model in ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "PozzettiAndrea",
|
||||
"title": "ComfyUI-AnyTop [REMOVED]",
|
||||
"reference": "https://github.com/PozzettiAndrea/ComfyUI-AnyTop",
|
||||
"files": [
|
||||
"https://github.com/PozzettiAndrea/ComfyUI-AnyTop"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Standalone ComfyUI custom nodes for AnyTop - Universal Motion Generation for Any Skeleton Topology."
|
||||
},
|
||||
{
|
||||
"author": "penposs",
|
||||
"title": "ComfyUI-Banana-Node [REMOVED]",
|
||||
"reference": "https://github.com/penposs/ComfyUI-Banana-Node",
|
||||
"files": [
|
||||
"https://github.com/penposs/ComfyUI-Banana-Node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that generates images using Google’s Gemini 2.5 Flash Image Preview API."
|
||||
},
|
||||
{
|
||||
"author": "spiralmountain",
|
||||
"title": "ComfyUI_HDNodes [REMOVED]",
|
||||
"reference": "https://github.com/spiralmountain/ComfyUI_HDNodes",
|
||||
"files": [
|
||||
"https://github.com/spiralmountain/ComfyUI_HDNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for ComfyUI that enable video generation using ByteDance's Seedance model via [a/Fal.ai](https://fal.ai/)."
|
||||
},
|
||||
{
|
||||
"author": "fredconex",
|
||||
"title": "Sync Edit [REMOVED]",
|
||||
"reference": "https://github.com/fredconex/ComfyUI-SyncEdit",
|
||||
"files": [
|
||||
"https://github.com/fredconex/ComfyUI-SyncEdit"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This node allow to intercept changes on the input string and choose between use the current one or sync with incoming new one."
|
||||
},
|
||||
{
|
||||
"author": "fredconex",
|
||||
"title": "ComfyUI-SoundFlow [REMOVED]",
|
||||
"reference": "https://github.com/fredconex/ComfyUI-SoundFlow",
|
||||
"files": [
|
||||
"https://github.com/fredconex/ComfyUI-SoundFlow"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is a bunch of nodes for ComfyUI to help with sound work."
|
||||
},
|
||||
{
|
||||
"author": "fredconex",
|
||||
"title": "SongBloom [REMOVED]",
|
||||
"reference": "https://github.com/fredconex/ComfyUI-SongBloom",
|
||||
"files": [
|
||||
"https://github.com/fredconex/ComfyUI-SongBloom"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI Nodes for SongBloom"
|
||||
},
|
||||
{
|
||||
"author": "EQXai",
|
||||
"title": "ComfyUI_EQX [REMOVED]",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-manager"
|
||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||
version = "3.37.1"
|
||||
version = "3.37.2"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
||||
|
||||
|
||||
132
scanner.py
132
scanner.py
@ -78,36 +78,14 @@ Examples:
|
||||
return args
|
||||
|
||||
|
||||
# Parse arguments
|
||||
args = parse_arguments()
|
||||
|
||||
# Determine mode
|
||||
scan_only_mode = args.scan_only is not None
|
||||
url_list_file = args.scan_only if scan_only_mode else None
|
||||
|
||||
# Determine temp_dir
|
||||
if args.temp_dir:
|
||||
temp_dir = args.temp_dir
|
||||
elif args.temp_dir_positional:
|
||||
temp_dir = args.temp_dir_positional
|
||||
else:
|
||||
temp_dir = os.path.join(os.getcwd(), ".tmp")
|
||||
|
||||
if not os.path.exists(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
|
||||
# Determine skip flags
|
||||
skip_update = args.skip_update or args.skip_all
|
||||
skip_stat_update = args.skip_stat_update or args.skip_all or scan_only_mode
|
||||
|
||||
if not skip_stat_update:
|
||||
auth = Auth.Token(os.environ.get('GITHUB_TOKEN'))
|
||||
g = Github(auth=auth)
|
||||
else:
|
||||
g = None
|
||||
|
||||
|
||||
print(f"TEMP DIR: {temp_dir}")
|
||||
# Module-level variables (will be set in main if running as script)
|
||||
args = None
|
||||
scan_only_mode = False
|
||||
url_list_file = None
|
||||
temp_dir = None
|
||||
skip_update = False
|
||||
skip_stat_update = True
|
||||
g = None
|
||||
|
||||
|
||||
parse_cnt = 0
|
||||
@ -127,10 +105,17 @@ def extract_nodes(code_text):
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
parsed_code = ast.parse(code_text)
|
||||
|
||||
assignments = (node for node in parsed_code.body if isinstance(node, ast.Assign))
|
||||
# Support both ast.Assign and ast.AnnAssign (for type-annotated assignments)
|
||||
assignments = (node for node in parsed_code.body if isinstance(node, (ast.Assign, ast.AnnAssign)))
|
||||
|
||||
for assignment in assignments:
|
||||
if isinstance(assignment.targets[0], ast.Name) and assignment.targets[0].id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||
# Handle ast.AnnAssign (e.g., NODE_CLASS_MAPPINGS: Type = {...})
|
||||
if isinstance(assignment, ast.AnnAssign):
|
||||
if isinstance(assignment.target, ast.Name) and assignment.target.id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||
node_class_mappings = assignment.value
|
||||
break
|
||||
# Handle ast.Assign (e.g., NODE_CLASS_MAPPINGS = {...})
|
||||
elif isinstance(assignment.targets[0], ast.Name) and assignment.targets[0].id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||
node_class_mappings = assignment.value
|
||||
break
|
||||
else:
|
||||
@ -250,7 +235,8 @@ def scan_in_file(filename, is_builtin=False):
|
||||
with open(filename, encoding='utf-8', errors='ignore') as file:
|
||||
code = file.read()
|
||||
|
||||
pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
|
||||
# Support type annotations (e.g., NODE_CLASS_MAPPINGS: Type = {...}) and line continuations (\)
|
||||
pattern = r"_CLASS_MAPPINGS\s*(?::\s*\w+\s*)?=\s*(?:\\\s*)?{([^}]*)}"
|
||||
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
||||
|
||||
nodes = set()
|
||||
@ -482,21 +468,21 @@ def update_custom_nodes(scan_only_mode=False, url_list_file=None):
|
||||
raise ValueError("url_list_file is required in scan-only mode")
|
||||
|
||||
git_url_titles_preemptions = get_urls_from_list_file(url_list_file)
|
||||
print(f"\n[Scan-Only Mode]")
|
||||
print("\n[Scan-Only Mode]")
|
||||
print(f" - URL source: {url_list_file}")
|
||||
print(f" - GitHub stats: DISABLED")
|
||||
print(" - GitHub stats: DISABLED")
|
||||
print(f" - Git clone/pull: {'ENABLED' if not skip_update else 'DISABLED'}")
|
||||
print(f" - Metadata: EMPTY")
|
||||
print(" - Metadata: EMPTY")
|
||||
else:
|
||||
if not os.path.exists('custom-node-list.json'):
|
||||
raise FileNotFoundError("custom-node-list.json not found")
|
||||
|
||||
git_url_titles_preemptions = get_git_urls_from_json('custom-node-list.json')
|
||||
print(f"\n[Standard Mode]")
|
||||
print(f" - URL source: custom-node-list.json")
|
||||
print("\n[Standard Mode]")
|
||||
print(" - URL source: custom-node-list.json")
|
||||
print(f" - GitHub stats: {'ENABLED' if not skip_stat_update else 'DISABLED'}")
|
||||
print(f" - Git clone/pull: {'ENABLED' if not skip_update else 'DISABLED'}")
|
||||
print(f" - Metadata: FULL")
|
||||
print(" - Metadata: FULL")
|
||||
|
||||
def process_git_url_title(url, title, preemptions, node_pattern):
|
||||
name = os.path.basename(url)
|
||||
@ -689,7 +675,14 @@ def gen_json(node_info, scan_only_mode=False):
|
||||
|
||||
data[git_url] = (nodes, metadata)
|
||||
else:
|
||||
print(f"WARN: {dirname} is removed from custom-node-list.json")
|
||||
# Scan-only mode: Repository not in node_info (expected behavior)
|
||||
# Construct URL from dirname (author_repo format)
|
||||
if '_' in dirname:
|
||||
parts = dirname.split('_', 1)
|
||||
git_url = f"https://github.com/{parts[0]}/{parts[1]}"
|
||||
data[git_url] = (nodes, metadata)
|
||||
else:
|
||||
print(f"WARN: {dirname} is removed from custom-node-list.json")
|
||||
|
||||
for file in node_files:
|
||||
nodes, metadata = scan_in_file(file)
|
||||
@ -775,24 +768,53 @@ def gen_json(node_info, scan_only_mode=False):
|
||||
json.dump(data, file, indent=4, sort_keys=True)
|
||||
|
||||
|
||||
print("### ComfyUI Manager Node Scanner ###")
|
||||
if __name__ == "__main__":
|
||||
# Parse arguments
|
||||
args = parse_arguments()
|
||||
|
||||
if scan_only_mode:
|
||||
print(f"\n# [Scan-Only Mode] Processing URL list: {url_list_file}\n")
|
||||
else:
|
||||
print("\n# [Standard Mode] Updating extensions\n")
|
||||
# Determine mode
|
||||
scan_only_mode = args.scan_only is not None
|
||||
url_list_file = args.scan_only if scan_only_mode else None
|
||||
|
||||
# Update/clone repositories and collect node info
|
||||
updated_node_info = update_custom_nodes(scan_only_mode, url_list_file)
|
||||
# Determine temp_dir
|
||||
if args.temp_dir:
|
||||
temp_dir = args.temp_dir
|
||||
elif args.temp_dir_positional:
|
||||
temp_dir = args.temp_dir_positional
|
||||
else:
|
||||
temp_dir = os.path.join(os.getcwd(), ".tmp")
|
||||
|
||||
print("\n# Generating 'extension-node-map.json'...\n")
|
||||
if not os.path.exists(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
|
||||
# Generate extension-node-map.json
|
||||
gen_json(updated_node_info, scan_only_mode)
|
||||
# Determine skip flags
|
||||
skip_update = args.skip_update or args.skip_all
|
||||
skip_stat_update = args.skip_stat_update or args.skip_all or scan_only_mode
|
||||
|
||||
print("\n✅ DONE.\n")
|
||||
if not skip_stat_update:
|
||||
auth = Auth.Token(os.environ.get('GITHUB_TOKEN'))
|
||||
g = Github(auth=auth)
|
||||
else:
|
||||
g = None
|
||||
|
||||
if scan_only_mode:
|
||||
print("Output: extension-node-map.json (node mappings only)")
|
||||
else:
|
||||
print("Output: extension-node-map.json (full metadata)")
|
||||
print("### ComfyUI Manager Node Scanner ###")
|
||||
|
||||
if scan_only_mode:
|
||||
print(f"\n# [Scan-Only Mode] Processing URL list: {url_list_file}\n")
|
||||
else:
|
||||
print("\n# [Standard Mode] Updating extensions\n")
|
||||
|
||||
# Update/clone repositories and collect node info
|
||||
updated_node_info = update_custom_nodes(scan_only_mode, url_list_file)
|
||||
|
||||
print("\n# Generating 'extension-node-map.json'...\n")
|
||||
|
||||
# Generate extension-node-map.json
|
||||
gen_json(updated_node_info, scan_only_mode)
|
||||
|
||||
print("\n✅ DONE.\n")
|
||||
|
||||
if scan_only_mode:
|
||||
print("Output: extension-node-map.json (node mappings only)")
|
||||
else:
|
||||
print("Output: extension-node-map.json (full metadata)")
|
||||
Loading…
Reference in New Issue
Block a user