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