setupcomfyuimodels 1.0.3 → 1.2.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/dist/index.js +105 -48
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/downloadAssets.ts +45 -16
- package/src/downloadAssets.ts~ +192 -0
- package/src/getConfig.ts +17 -4
- package/src/getConfig.ts~ +11 -0
- package/src/index.ts +7 -2
- package/src/index.ts~ +14 -0
- package/src/replacements.ts +37 -0
- package/src/replacements.ts~ +0 -0
package/package.json
CHANGED
package/src/downloadAssets.ts
CHANGED
|
@@ -9,28 +9,40 @@ import {
|
|
|
9
9
|
stopProgressBar,
|
|
10
10
|
updateKey,
|
|
11
11
|
} from "./progressbar";
|
|
12
|
-
import {
|
|
12
|
+
import { Config } from "./getConfig";
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
if (!WORKSPACE) {
|
|
18
|
-
throw new Error("WORKSPACE environment variable is not set.");
|
|
19
|
-
}
|
|
15
|
+
const { CIVITAI_TOKEN, HF_TOKEN } = process.env;
|
|
20
16
|
|
|
21
17
|
const MAX_CONCURRENT_DOWNLOADS = 8;
|
|
22
18
|
let concurrentDownloads = 0;
|
|
23
19
|
|
|
24
|
-
export const downloadAssets = async (
|
|
25
|
-
const
|
|
20
|
+
export const downloadAssets = async (config: Config, targetFolder: string) => {
|
|
21
|
+
const filesConfigs = [
|
|
22
|
+
config.files,
|
|
23
|
+
...Object.entries(config.groups)
|
|
24
|
+
.filter(([groupName]) => config.groupsEnabled[groupName])
|
|
25
|
+
.map(([_, f]) => f.files),
|
|
26
|
+
];
|
|
27
|
+
const foldersConfigs = [
|
|
28
|
+
config.folders,
|
|
29
|
+
...Object.entries(config.groups)
|
|
30
|
+
.filter(([groupName]) => config.groupsEnabled[groupName])
|
|
31
|
+
.map(([_, f]) => f.folders),
|
|
32
|
+
];
|
|
33
|
+
|
|
26
34
|
const filePromises = [
|
|
27
|
-
...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
...filesConfigs.flatMap((config) =>
|
|
36
|
+
Object.entries(config.files).map(([targetLocation, url]) => {
|
|
37
|
+
const fileName = path.basename(targetLocation),
|
|
38
|
+
dir = path.dirname(targetLocation);
|
|
39
|
+
return scheduleDownload(targetFolder, dir, fileName, url);
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
...foldersConfigs.flatMap((config) =>
|
|
43
|
+
Object.entries(config).flatMap(([folder, urls]) =>
|
|
44
|
+
urls.map((url) => scheduleDownload(targetFolder, folder, null, url)),
|
|
45
|
+
),
|
|
34
46
|
),
|
|
35
47
|
];
|
|
36
48
|
setTotalProgress(0, filePromises.length);
|
|
@@ -76,10 +88,25 @@ const results = [] as {
|
|
|
76
88
|
error?: string;
|
|
77
89
|
}[];
|
|
78
90
|
const scheduleDownload = async (
|
|
91
|
+
globalTargetLocation: string,
|
|
79
92
|
targetLocation: string,
|
|
80
93
|
targetFileName: string | null,
|
|
81
94
|
url: string,
|
|
82
95
|
) => {
|
|
96
|
+
const matchingJob = scheduledDownloads.find(
|
|
97
|
+
(d) =>
|
|
98
|
+
d.targetLocation === targetLocation &&
|
|
99
|
+
d.targetFileName === targetFileName,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (matchingJob) {
|
|
103
|
+
if (matchingJob.url !== url) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Conflicting download jobs for ${targetLocation}/${targetFileName || ""}: ${matchingJob.url} and ${url}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
83
110
|
scheduledDownloads.push({ targetLocation, url, targetFileName });
|
|
84
111
|
if (concurrentDownloads < MAX_CONCURRENT_DOWNLOADS) {
|
|
85
112
|
while (scheduledDownloads.length > 0) {
|
|
@@ -88,6 +115,7 @@ const scheduleDownload = async (
|
|
|
88
115
|
concurrentDownloads++;
|
|
89
116
|
try {
|
|
90
117
|
const skipped = await downloadFile(
|
|
118
|
+
globalTargetLocation,
|
|
91
119
|
nextDownload.targetLocation,
|
|
92
120
|
nextDownload.targetFileName,
|
|
93
121
|
nextDownload.url,
|
|
@@ -111,6 +139,7 @@ const scheduleDownload = async (
|
|
|
111
139
|
};
|
|
112
140
|
|
|
113
141
|
const downloadFile = async (
|
|
142
|
+
globalTargetLocation: string,
|
|
114
143
|
targetLocation: string,
|
|
115
144
|
targetFileName: string | null,
|
|
116
145
|
url: string,
|
|
@@ -118,7 +147,7 @@ const downloadFile = async (
|
|
|
118
147
|
const getTargetId = () => path.join(targetLocation, targetFileName || "");
|
|
119
148
|
try {
|
|
120
149
|
const targetDir = path.normalize(
|
|
121
|
-
`${
|
|
150
|
+
`${globalTargetLocation}/${targetLocation}`,
|
|
122
151
|
);
|
|
123
152
|
|
|
124
153
|
fs.mkdirSync(targetDir, { recursive: true });
|
package/src/downloadAssets.ts~
CHANGED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Readable } from "stream";
|
|
4
|
+
import {
|
|
5
|
+
incrementTotalProgress,
|
|
6
|
+
log,
|
|
7
|
+
removeKey,
|
|
8
|
+
setTotalProgress,
|
|
9
|
+
stopProgressBar,
|
|
10
|
+
updateKey,
|
|
11
|
+
} from "./progressbar";
|
|
12
|
+
import { getConfig } from "./getConfig";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
|
|
15
|
+
const { CIVITAI_TOKEN, HF_TOKEN } = process.env;
|
|
16
|
+
|
|
17
|
+
const MAX_CONCURRENT_DOWNLOADS = 8;
|
|
18
|
+
let concurrentDownloads = 0;
|
|
19
|
+
|
|
20
|
+
export const downloadAssets = async (
|
|
21
|
+
configLocation: string,
|
|
22
|
+
targetFolder: string,
|
|
23
|
+
) => {
|
|
24
|
+
const config = await getConfig(configLocation);
|
|
25
|
+
const filePromises = [
|
|
26
|
+
...Object.entries(config.files).map(([targetLocation, url]) => {
|
|
27
|
+
const fileName = path.basename(targetLocation),
|
|
28
|
+
dir = path.dirname(targetLocation);
|
|
29
|
+
return scheduleDownload(targetFolder, dir, fileName, url);
|
|
30
|
+
}),
|
|
31
|
+
...Object.entries(config.folders).flatMap(([folder, urls]) =>
|
|
32
|
+
urls.map((url) => scheduleDownload(targetFolder, folder, null, url)),
|
|
33
|
+
),
|
|
34
|
+
];
|
|
35
|
+
setTotalProgress(0, filePromises.length);
|
|
36
|
+
await Promise.all(filePromises);
|
|
37
|
+
stopProgressBar();
|
|
38
|
+
console.log(
|
|
39
|
+
chalk.bgWhite("Skipped downloads:\n"),
|
|
40
|
+
results
|
|
41
|
+
.filter((r) => r.skipped)
|
|
42
|
+
.map((r) => `${r.targetLocation}/${r.targetFileName || ""} from ${r.url}`)
|
|
43
|
+
.join("\n"),
|
|
44
|
+
);
|
|
45
|
+
console.log(
|
|
46
|
+
chalk.bgWhite("Successful downloads:\n"),
|
|
47
|
+
results
|
|
48
|
+
.filter((r) => r.downloaded)
|
|
49
|
+
.map((r) => `${r.targetLocation}/${r.targetFileName || ""} from ${r.url}`)
|
|
50
|
+
.join("\n"),
|
|
51
|
+
);
|
|
52
|
+
console.log(
|
|
53
|
+
chalk.bgWhite("Download errors:\n"),
|
|
54
|
+
results
|
|
55
|
+
.filter((r) => r.error)
|
|
56
|
+
.map(
|
|
57
|
+
(r) =>
|
|
58
|
+
`${r.targetLocation}/${r.targetFileName || ""} from ${r.url} had error: ${r.error}`,
|
|
59
|
+
)
|
|
60
|
+
.join("\n"),
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const scheduledDownloads = [] as {
|
|
65
|
+
targetLocation: string;
|
|
66
|
+
targetFileName: string | null;
|
|
67
|
+
url: string;
|
|
68
|
+
}[];
|
|
69
|
+
const results = [] as {
|
|
70
|
+
targetLocation: string;
|
|
71
|
+
targetFileName: string | null;
|
|
72
|
+
url: string;
|
|
73
|
+
skipped?: boolean;
|
|
74
|
+
downloaded?: boolean;
|
|
75
|
+
error?: string;
|
|
76
|
+
}[];
|
|
77
|
+
const scheduleDownload = async (
|
|
78
|
+
globalTargetLocation: string,
|
|
79
|
+
targetLocation: string,
|
|
80
|
+
targetFileName: string | null,
|
|
81
|
+
url: string,
|
|
82
|
+
) => {
|
|
83
|
+
scheduledDownloads.push({ targetLocation, url, targetFileName });
|
|
84
|
+
if (concurrentDownloads < MAX_CONCURRENT_DOWNLOADS) {
|
|
85
|
+
while (scheduledDownloads.length > 0) {
|
|
86
|
+
const nextDownload = scheduledDownloads.shift();
|
|
87
|
+
if (nextDownload) {
|
|
88
|
+
concurrentDownloads++;
|
|
89
|
+
try {
|
|
90
|
+
const skipped = await downloadFile(
|
|
91
|
+
globalTargetLocation,
|
|
92
|
+
nextDownload.targetLocation,
|
|
93
|
+
nextDownload.targetFileName,
|
|
94
|
+
nextDownload.url,
|
|
95
|
+
);
|
|
96
|
+
results.push({
|
|
97
|
+
...nextDownload,
|
|
98
|
+
skipped,
|
|
99
|
+
downloaded: !skipped,
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
results.push({
|
|
103
|
+
...nextDownload,
|
|
104
|
+
error: (error as Error).message,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
incrementTotalProgress();
|
|
108
|
+
concurrentDownloads--;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const downloadFile = async (
|
|
115
|
+
globalTargetLocation: string,
|
|
116
|
+
targetLocation: string,
|
|
117
|
+
targetFileName: string | null,
|
|
118
|
+
url: string,
|
|
119
|
+
) => {
|
|
120
|
+
const getTargetId = () => path.join(targetLocation, targetFileName || "");
|
|
121
|
+
try {
|
|
122
|
+
const targetDir = path.normalize(
|
|
123
|
+
`${globalTargetLocation}/${targetLocation}`,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
127
|
+
if (targetFileName && fs.existsSync(path.join(targetDir, targetFileName))) {
|
|
128
|
+
log(`File already exists, skipping: ${getTargetId()}`);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
const isCivitAi = url.match(/https:\/\/civitai.com/);
|
|
132
|
+
const isHugging = url.match(/https:\/\/huggingface.co/);
|
|
133
|
+
|
|
134
|
+
const headers: Record<string, string> = {};
|
|
135
|
+
if (isCivitAi && CIVITAI_TOKEN) {
|
|
136
|
+
headers["Authorization"] = `Bearer ${CIVITAI_TOKEN}`;
|
|
137
|
+
}
|
|
138
|
+
if (isHugging && HF_TOKEN) {
|
|
139
|
+
headers["Authorization"] = `Bearer ${HF_TOKEN}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const response = await fetch(url, { headers });
|
|
143
|
+
if (!response.ok || !response.body) {
|
|
144
|
+
log(`Error for url: ${url}`);
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Failed to download ${url}: ${response.status} ${response.statusText}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const total = Number(response.headers.get("content-length"));
|
|
150
|
+
const contentDisposition = response.headers.get("content-disposition");
|
|
151
|
+
if (!targetFileName) {
|
|
152
|
+
const fileNameMatch = contentDisposition?.match(
|
|
153
|
+
/filename="?(.+?)"?($|;)/,
|
|
154
|
+
);
|
|
155
|
+
if (fileNameMatch) {
|
|
156
|
+
targetFileName = fileNameMatch[1];
|
|
157
|
+
} else {
|
|
158
|
+
log(`Error for url: ${url}`);
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Failed to extract filename from content-disposition for ${url}. Provide filename.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const filePath = path.join(targetDir, targetFileName);
|
|
165
|
+
if (fs.existsSync(filePath)) {
|
|
166
|
+
log(`File already exists, skipping: ${getTargetId()}`);
|
|
167
|
+
response.body?.cancel();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
log(`Downloading ${getTargetId()} from ${url}`);
|
|
171
|
+
|
|
172
|
+
updateKey(getTargetId(), 0, total);
|
|
173
|
+
const readableNodeStream = Readable.fromWeb(response.body);
|
|
174
|
+
const fileStream = fs.createWriteStream(filePath + ".part");
|
|
175
|
+
|
|
176
|
+
let current = 0;
|
|
177
|
+
await new Promise<void>((resolve, reject) => {
|
|
178
|
+
readableNodeStream.on("data", (chunk: Buffer) => {
|
|
179
|
+
current += chunk.length;
|
|
180
|
+
updateKey(getTargetId(), current, total);
|
|
181
|
+
});
|
|
182
|
+
readableNodeStream.pipe(fileStream);
|
|
183
|
+
readableNodeStream.on("error", reject);
|
|
184
|
+
fileStream.on("finish", resolve);
|
|
185
|
+
});
|
|
186
|
+
fs.renameSync(filePath + ".part", filePath);
|
|
187
|
+
log(`Done downloading: ${url}`);
|
|
188
|
+
return false;
|
|
189
|
+
} finally {
|
|
190
|
+
removeKey(getTargetId());
|
|
191
|
+
}
|
|
192
|
+
};
|
package/src/getConfig.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import { parse } from "comment-json";
|
|
3
3
|
|
|
4
|
+
type FilesAndFolders = {
|
|
5
|
+
files: Record<string, string>;
|
|
6
|
+
folders: Record<string, string[]>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type Config = {
|
|
10
|
+
groupsEnabled: Record<string, boolean>;
|
|
11
|
+
groups: Record<string, FilesAndFolders>;
|
|
12
|
+
replacements?: {
|
|
13
|
+
file: string;
|
|
14
|
+
original: string;
|
|
15
|
+
replacement: string;
|
|
16
|
+
line: number;
|
|
17
|
+
}[];
|
|
18
|
+
} & FilesAndFolders;
|
|
19
|
+
|
|
4
20
|
export const getConfig = async (configLocation: string) => {
|
|
5
21
|
const fileContent = readFileSync(configLocation, "utf-8");
|
|
6
22
|
|
|
7
|
-
return parse(fileContent) as unknown as
|
|
8
|
-
files: Record<string, string>;
|
|
9
|
-
folders: Record<string, string[]>;
|
|
10
|
-
};
|
|
23
|
+
return parse(fileContent) as unknown as Config;
|
|
11
24
|
};
|
package/src/getConfig.ts~
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { parse } from "comment-json";
|
|
3
|
+
|
|
4
|
+
export const getConfig = async (configLocation: string) => {
|
|
5
|
+
const fileContent = readFileSync(configLocation, "utf-8");
|
|
6
|
+
|
|
7
|
+
return parse(fileContent) as unknown as {
|
|
8
|
+
files: Record<string, string>;
|
|
9
|
+
folders: Record<string, string[]>;
|
|
10
|
+
};
|
|
11
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "@commander-js/extra-typings";
|
|
3
3
|
import { downloadAssets } from "./downloadAssets";
|
|
4
|
+
import { replaceInFiles } from "./replacements";
|
|
5
|
+
import { getConfig } from "./getConfig";
|
|
4
6
|
|
|
5
7
|
const program = new Command();
|
|
6
8
|
program.name("Setup ComfyUi Models");
|
|
7
9
|
program
|
|
8
10
|
.command("download")
|
|
9
11
|
.argument("<config>", "JSON config file location")
|
|
10
|
-
.
|
|
11
|
-
|
|
12
|
+
.option("-l, --location <path>", "Target download location")
|
|
13
|
+
.action(async (configLocation: string, options) => {
|
|
14
|
+
const config = await getConfig(configLocation);
|
|
15
|
+
await downloadAssets(config, options.location || "./");
|
|
16
|
+
replaceInFiles(config, options.location || "./");
|
|
12
17
|
});
|
|
13
18
|
program.parse();
|
package/src/index.ts~
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "@commander-js/extra-typings";
|
|
3
|
+
import { downloadAssets } from "./downloadAssets";
|
|
4
|
+
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program.name("Setup ComfyUi Models");
|
|
7
|
+
program
|
|
8
|
+
.command("download")
|
|
9
|
+
.argument("<config>", "JSON config file location")
|
|
10
|
+
.option("-l, --location <path>", "Target download location")
|
|
11
|
+
.action(async (configLocation: string, options) => {
|
|
12
|
+
await downloadAssets(configLocation, options.location || "./");
|
|
13
|
+
});
|
|
14
|
+
program.parse();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { Config } from "./getConfig";
|
|
4
|
+
|
|
5
|
+
export const replaceInFiles = (config: Config, targetFolder: string) => {
|
|
6
|
+
const replacements = config.replacements || [];
|
|
7
|
+
for (const replacement of replacements) {
|
|
8
|
+
const filePath = path.join(targetFolder, replacement.file);
|
|
9
|
+
if (fs.existsSync(filePath)) {
|
|
10
|
+
let fileContent = fs.readFileSync(filePath, "utf-8");
|
|
11
|
+
if (fileContent.includes(replacement.original)) {
|
|
12
|
+
const lines = fileContent.split("\n");
|
|
13
|
+
if (
|
|
14
|
+
lines.length >= replacement.line &&
|
|
15
|
+
lines[replacement.line - 1].includes(replacement.original)
|
|
16
|
+
) {
|
|
17
|
+
lines[replacement.line - 1] = lines[replacement.line - 1].replace(
|
|
18
|
+
replacement.original,
|
|
19
|
+
replacement.replacement,
|
|
20
|
+
);
|
|
21
|
+
fileContent = lines.join("\n");
|
|
22
|
+
} else {
|
|
23
|
+
console.warn(
|
|
24
|
+
`Line number mismatch in ${replacement.file}. Expected line ${replacement.line} to contain the original code.`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fs.writeFileSync(filePath, fileContent, "utf-8");
|
|
29
|
+
console.log(`Patched ${replacement.file} at line ${replacement.line}`);
|
|
30
|
+
} else {
|
|
31
|
+
console.warn(`Original code not found in ${replacement.file}`);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
console.warn(`File not found: ${replacement.file}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
File without changes
|