cyclops-infobook-html 5.1.0 → 5.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/README.md +12 -12
- package/bin/compress-icons.js +42 -7
- package/bin/convert-pom-to-modpack.d.ts +2 -0
- package/bin/convert-pom-to-modpack.js +96 -0
- package/bin/generate-icons.js +50 -15
- package/bin/generate-infobook-html.js +46 -10
- package/bin/generate-mod-metadata.js +52 -14
- package/index.d.ts +25 -25
- package/lib/icon/IconsCompressor.js +43 -12
- package/lib/icon/IconsGenerator.d.ts +42 -20
- package/lib/icon/IconsGenerator.js +156 -70
- package/lib/infobook/FileWriter.d.ts +3 -3
- package/lib/infobook/FileWriter.js +5 -5
- package/lib/infobook/IFileWriter.d.ts +2 -2
- package/lib/infobook/IInfoAppendix.d.ts +3 -3
- package/lib/infobook/IInfoBook.d.ts +2 -4
- package/lib/infobook/IInfoSection.d.ts +1 -1
- package/lib/infobook/IInfobookPlugin.d.ts +5 -5
- package/lib/infobook/InfoBookInitializer.d.ts +7 -9
- package/lib/infobook/InfoBookInitializer.js +1 -1
- package/lib/infobook/appendix/IInfoBookAppendixHandler.d.ts +2 -2
- package/lib/infobook/appendix/InfoBookAppendixAd.d.ts +3 -3
- package/lib/infobook/appendix/InfoBookAppendixAd.js +7 -2
- package/lib/infobook/appendix/InfoBookAppendixHandlerAbstractRecipe.d.ts +12 -14
- package/lib/infobook/appendix/InfoBookAppendixHandlerAbstractRecipe.js +41 -8
- package/lib/infobook/appendix/InfoBookAppendixHandlerAdvancementRewards.d.ts +3 -3
- package/lib/infobook/appendix/InfoBookAppendixHandlerAdvancementRewards.js +5 -4
- package/lib/infobook/appendix/InfoBookAppendixHandlerCraftingRecipe.d.ts +6 -5
- package/lib/infobook/appendix/InfoBookAppendixHandlerCraftingRecipe.js +4 -3
- package/lib/infobook/appendix/InfoBookAppendixHandlerImage.d.ts +3 -3
- package/lib/infobook/appendix/InfoBookAppendixHandlerImage.js +4 -4
- package/lib/infobook/appendix/InfoBookAppendixHandlerKeybinding.d.ts +3 -3
- package/lib/infobook/appendix/InfoBookAppendixHandlerKeybinding.js +3 -2
- package/lib/infobook/appendix/InfoBookAppendixHandlerSmeltingRecipe.d.ts +6 -5
- package/lib/infobook/appendix/InfoBookAppendixHandlerSmeltingRecipe.js +3 -2
- package/lib/infobook/appendix/InfoBookAppendixHandlerTextfield.d.ts +3 -3
- package/lib/infobook/appendix/InfoBookAppendixHandlerTextfield.js +4 -5
- package/lib/infobook/appendix/InfoBookAppendixTagIndex.d.ts +4 -4
- package/lib/infobook/appendix/InfoBookAppendixTagIndex.js +2 -1
- package/lib/modloader/ModLoader.d.ts +13 -2
- package/lib/modloader/ModLoader.js +164 -69
- package/lib/modloader/PomConverter.d.ts +19 -0
- package/lib/modloader/PomConverter.js +138 -0
- package/lib/parse/XmlInfoBookParser.d.ts +5 -7
- package/lib/parse/XmlInfoBookParser.js +42 -9
- package/lib/resource/ResourceHandler.d.ts +7 -13
- package/lib/resource/ResourceHandler.js +16 -11
- package/lib/resource/ResourceLoader.d.ts +4 -5
- package/lib/resource/ResourceLoader.js +35 -33
- package/lib/serialize/HtmlInfoBookSerializer.d.ts +10 -16
- package/lib/serialize/HtmlInfoBookSerializer.js +83 -80
- package/package.json +33 -27
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -10,11 +43,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
43
|
};
|
|
11
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
45
|
exports.IconsCompressor = void 0;
|
|
13
|
-
const
|
|
14
|
-
const fs = require("fs");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const execFileAsync = (0,
|
|
46
|
+
const node_child_process_1 = require("node:child_process");
|
|
47
|
+
const fs = __importStar(require("node:fs"));
|
|
48
|
+
const node_path_1 = require("node:path");
|
|
49
|
+
const node_util_1 = require("node:util");
|
|
50
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
18
51
|
/**
|
|
19
52
|
* Losslessly compresses PNG icon files using OptiPNG.
|
|
20
53
|
* Mimics how ImgBot compresses images: lossless PNG compression at optimization level 7
|
|
@@ -37,7 +70,7 @@ class IconsCompressor {
|
|
|
37
70
|
throw new Error(`OptiPNG binary not found at: ${optipngPath}. Ensure optipng-bin is installed.`);
|
|
38
71
|
}
|
|
39
72
|
const files = yield fs.promises.readdir(this.iconsDir);
|
|
40
|
-
const pngFiles = files.filter(
|
|
73
|
+
const pngFiles = files.filter(f => f.endsWith('.png'));
|
|
41
74
|
if (pngFiles.length === 0) {
|
|
42
75
|
process.stdout.write(`No PNG files found in ${this.iconsDir}\n`);
|
|
43
76
|
return;
|
|
@@ -47,7 +80,7 @@ class IconsCompressor {
|
|
|
47
80
|
let errors = 0;
|
|
48
81
|
let totalSavedBytes = 0;
|
|
49
82
|
for (const file of pngFiles) {
|
|
50
|
-
const filePath = (0,
|
|
83
|
+
const filePath = (0, node_path_1.join)(this.iconsDir, file);
|
|
51
84
|
const sizeBefore = fs.statSync(filePath).size;
|
|
52
85
|
try {
|
|
53
86
|
yield execFileAsync(optipngPath, ['-o7', '-strip', 'all', '-quiet', filePath]);
|
|
@@ -61,9 +94,7 @@ class IconsCompressor {
|
|
|
61
94
|
}
|
|
62
95
|
}
|
|
63
96
|
const savedKb = (totalSavedBytes / 1024).toFixed(1);
|
|
64
|
-
process.stdout.write(`Compressed ${compressed} icons (saved ${savedKb} KB)`
|
|
65
|
-
(errors > 0 ? `, ${errors} errors` : '') +
|
|
66
|
-
`\n`);
|
|
97
|
+
process.stdout.write(`Compressed ${compressed} icons (saved ${savedKb} KB)${errors > 0 ? `, ${errors} errors` : ''}\n`);
|
|
67
98
|
});
|
|
68
99
|
}
|
|
69
100
|
/**
|
|
@@ -72,9 +103,9 @@ class IconsCompressor {
|
|
|
72
103
|
*/
|
|
73
104
|
getOptipngPath() {
|
|
74
105
|
const indexPath = require.resolve('optipng-bin');
|
|
75
|
-
const pkgRoot = (0,
|
|
106
|
+
const pkgRoot = (0, node_path_1.dirname)(indexPath);
|
|
76
107
|
const binaryName = process.platform === 'win32' ? 'optipng.exe' : 'optipng';
|
|
77
|
-
return (0,
|
|
108
|
+
return (0, node_path_1.join)(pkgRoot, 'vendor', binaryName);
|
|
78
109
|
}
|
|
79
110
|
}
|
|
80
111
|
exports.IconsCompressor = IconsCompressor;
|
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
* runs the iconexporter export command, and copies the resulting icons.
|
|
6
6
|
*/
|
|
7
7
|
export declare class IconsGenerator {
|
|
8
|
-
private static readonly
|
|
9
|
-
private static readonly
|
|
10
|
-
private static readonly
|
|
11
|
-
private static readonly
|
|
12
|
-
private static readonly
|
|
13
|
-
private static readonly
|
|
14
|
-
private static readonly
|
|
8
|
+
private static readonly headlessMcJar;
|
|
9
|
+
private static readonly iconExporterJar;
|
|
10
|
+
private static readonly hmcGameSubdir;
|
|
11
|
+
private static readonly hmcConfigSubdir;
|
|
12
|
+
private static readonly iconExportSubdir;
|
|
13
|
+
private static readonly defaultIconSize;
|
|
14
|
+
private static readonly hmcSpecificsRepo;
|
|
15
|
+
private static readonly modrinthApiBase;
|
|
16
|
+
private static readonly iconExporterModrinthSlug;
|
|
15
17
|
private readonly modsDir;
|
|
16
18
|
private readonly iconsDir;
|
|
17
19
|
private readonly workDir;
|
|
18
20
|
private readonly minecraftVersion;
|
|
19
21
|
private readonly neoforgeVersion;
|
|
20
|
-
private readonly githubToken;
|
|
21
|
-
private readonly iconExporterArtifact;
|
|
22
22
|
private readonly iconExporterVersion;
|
|
23
23
|
private readonly headlessMcVersion;
|
|
24
24
|
private readonly launchTimeoutMs;
|
|
@@ -32,9 +32,21 @@ export declare class IconsGenerator {
|
|
|
32
32
|
*/
|
|
33
33
|
downloadHeadlessMc(): Promise<void>;
|
|
34
34
|
/**
|
|
35
|
-
* Download the IconExporter mod from
|
|
35
|
+
* Download the IconExporter mod from Modrinth.
|
|
36
|
+
* If an explicit version is configured, fetches that specific version; otherwise fetches the
|
|
37
|
+
* latest version for the configured Minecraft version. No authentication is required.
|
|
36
38
|
*/
|
|
37
39
|
downloadIconExporter(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Fetch an IconExporter release from Modrinth for the configured Minecraft version and the
|
|
42
|
+
* NeoForge loader. If a specific version number is provided, that version is matched;
|
|
43
|
+
* otherwise the latest (newest) version is returned.
|
|
44
|
+
* Returns the primary file's download URL and filename.
|
|
45
|
+
*/
|
|
46
|
+
fetchIconExporterFromModrinth(version?: string): Promise<{
|
|
47
|
+
url: string;
|
|
48
|
+
filename: string;
|
|
49
|
+
}>;
|
|
38
50
|
/**
|
|
39
51
|
* Set up the game directory with mods and options.
|
|
40
52
|
*/
|
|
@@ -83,6 +95,23 @@ export declare class IconsGenerator {
|
|
|
83
95
|
downloadFile(url: string, destPath: string, headers?: Record<string, string>): Promise<void>;
|
|
84
96
|
}
|
|
85
97
|
export type IGameState = 'waiting_for_prompt' | 'game_launching' | 'checking_screen' | 'navigating_singleplayer' | 'creating_world' | 'exporting_icons' | 'quitting';
|
|
98
|
+
export interface IModrinthVersionFile {
|
|
99
|
+
url: string;
|
|
100
|
+
filename: string;
|
|
101
|
+
primary: boolean;
|
|
102
|
+
}
|
|
103
|
+
export interface IModrinthVersion {
|
|
104
|
+
version_number: string;
|
|
105
|
+
files: IModrinthVersionFile[];
|
|
106
|
+
}
|
|
107
|
+
export interface IGithubAsset {
|
|
108
|
+
name: string;
|
|
109
|
+
browser_download_url: string;
|
|
110
|
+
}
|
|
111
|
+
export interface IGithubRelease {
|
|
112
|
+
assets: IGithubAsset[];
|
|
113
|
+
tag_name: string;
|
|
114
|
+
}
|
|
86
115
|
export interface IIconsGeneratorArgs {
|
|
87
116
|
/**
|
|
88
117
|
* Directory containing mod JARs to include in the client (usually server/mods).
|
|
@@ -105,16 +134,9 @@ export interface IIconsGeneratorArgs {
|
|
|
105
134
|
*/
|
|
106
135
|
neoforgeVersion: string;
|
|
107
136
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
githubToken?: string;
|
|
112
|
-
/**
|
|
113
|
-
* Maven artifact ID for IconExporter (default: iconexporter-{minecraftVersion}-neoforge).
|
|
114
|
-
*/
|
|
115
|
-
iconExporterArtifact?: string;
|
|
116
|
-
/**
|
|
117
|
-
* Version of the IconExporter artifact to download (e.g., "1.4.0-174").
|
|
137
|
+
* Version of the IconExporter artifact to pin (e.g., "1.4.0-174").
|
|
138
|
+
* If not provided, the latest version for the configured Minecraft version is
|
|
139
|
+
* automatically fetched from Modrinth (no authentication required).
|
|
118
140
|
*/
|
|
119
141
|
iconExporterVersion?: string;
|
|
120
142
|
/**
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,13 +41,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
42
|
});
|
|
10
43
|
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
11
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
48
|
exports.IconsGenerator = void 0;
|
|
13
|
-
/*
|
|
14
|
-
const
|
|
15
|
-
const fs = require("fs");
|
|
16
|
-
const
|
|
17
|
-
const
|
|
49
|
+
/* Tslint:disable:ter-indent */
|
|
50
|
+
const node_child_process_1 = require("node:child_process");
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const node_path_1 = require("node:path");
|
|
53
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
18
54
|
/**
|
|
19
55
|
* Generates icons using the IconExporter mod and HeadlessMC.
|
|
20
56
|
* This class downloads HeadlessMC and the IconExporter mod, sets up a headless
|
|
@@ -40,11 +76,10 @@ class IconsGenerator {
|
|
|
40
76
|
this.workDir = args.workDir;
|
|
41
77
|
this.minecraftVersion = args.minecraftVersion;
|
|
42
78
|
this.neoforgeVersion = args.neoforgeVersion;
|
|
43
|
-
this.
|
|
44
|
-
this.iconExporterArtifact = args.iconExporterArtifact || `iconexporter-${args.minecraftVersion}-neoforge`;
|
|
45
|
-
this.iconExporterVersion = args.iconExporterVersion || '1.4.0-174';
|
|
79
|
+
this.iconExporterVersion = args.iconExporterVersion;
|
|
46
80
|
this.headlessMcVersion = args.headlessMcVersion || '2.8.0';
|
|
47
|
-
|
|
81
|
+
// 15 minutes
|
|
82
|
+
this.launchTimeoutMs = args.launchTimeoutMs || (15 * 60 * 1000);
|
|
48
83
|
}
|
|
49
84
|
/**
|
|
50
85
|
* Run the full icon generation pipeline.
|
|
@@ -74,7 +109,7 @@ class IconsGenerator {
|
|
|
74
109
|
*/
|
|
75
110
|
downloadHeadlessMc() {
|
|
76
111
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
-
const jarPath = (0,
|
|
112
|
+
const jarPath = (0, node_path_1.join)(this.workDir, IconsGenerator.headlessMcJar);
|
|
78
113
|
if (fs.existsSync(jarPath)) {
|
|
79
114
|
process.stdout.write('HeadlessMC already downloaded, skipping.\n');
|
|
80
115
|
return;
|
|
@@ -85,32 +120,78 @@ class IconsGenerator {
|
|
|
85
120
|
});
|
|
86
121
|
}
|
|
87
122
|
/**
|
|
88
|
-
* Download the IconExporter mod from
|
|
123
|
+
* Download the IconExporter mod from Modrinth.
|
|
124
|
+
* If an explicit version is configured, fetches that specific version; otherwise fetches the
|
|
125
|
+
* latest version for the configured Minecraft version. No authentication is required.
|
|
89
126
|
*/
|
|
90
127
|
downloadIconExporter() {
|
|
91
128
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
-
const jarPath = (0,
|
|
129
|
+
const jarPath = (0, node_path_1.join)(this.workDir, IconsGenerator.iconExporterJar);
|
|
93
130
|
if (fs.existsSync(jarPath)) {
|
|
94
131
|
process.stdout.write('IconExporter already downloaded, skipping.\n');
|
|
95
132
|
return;
|
|
96
133
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
if (this.iconExporterVersion) {
|
|
135
|
+
process.stdout.write(`Fetching IconExporter ${this.iconExporterVersion} for Minecraft ${this.minecraftVersion} from Modrinth...\n`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
process.stdout.write(`Fetching latest IconExporter version for Minecraft ${this.minecraftVersion} from Modrinth...\n`);
|
|
102
139
|
}
|
|
103
|
-
yield this.
|
|
140
|
+
const { url: downloadUrl, filename } = yield this.fetchIconExporterFromModrinth(this.iconExporterVersion);
|
|
141
|
+
process.stdout.write(`Found IconExporter: ${filename}\n`);
|
|
142
|
+
yield this.downloadFile(downloadUrl, jarPath);
|
|
104
143
|
process.stdout.write(`Downloaded IconExporter to ${jarPath}\n`);
|
|
105
144
|
});
|
|
106
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Fetch an IconExporter release from Modrinth for the configured Minecraft version and the
|
|
148
|
+
* NeoForge loader. If a specific version number is provided, that version is matched;
|
|
149
|
+
* otherwise the latest (newest) version is returned.
|
|
150
|
+
* Returns the primary file's download URL and filename.
|
|
151
|
+
*/
|
|
152
|
+
fetchIconExporterFromModrinth(version) {
|
|
153
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
154
|
+
const gameVersions = JSON.stringify([this.minecraftVersion]);
|
|
155
|
+
const loaders = JSON.stringify(['neoforge']);
|
|
156
|
+
const apiUrl = `${IconsGenerator.modrinthApiBase}/project/${IconsGenerator.iconExporterModrinthSlug}/version` +
|
|
157
|
+
`?game_versions=${encodeURIComponent(gameVersions)}&loaders=${encodeURIComponent(loaders)}`;
|
|
158
|
+
const response = yield (0, node_fetch_1.default)(apiUrl);
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(`Failed to fetch IconExporter versions from Modrinth: ${response.status} ${response.statusText}`);
|
|
161
|
+
}
|
|
162
|
+
const versions = yield response.json();
|
|
163
|
+
if (!versions || versions.length === 0) {
|
|
164
|
+
throw new Error(`No IconExporter versions found on Modrinth for Minecraft ${this.minecraftVersion} with NeoForge`);
|
|
165
|
+
}
|
|
166
|
+
let matched;
|
|
167
|
+
if (version) {
|
|
168
|
+
// Modrinth version_number is typically "{mcVersion}-{modVersion}" (e.g. "1.21.1-1.4.0-174").
|
|
169
|
+
// Match either an exact version_number or the canonical "<mcVersion>-<version>" form.
|
|
170
|
+
const canonicalVersionNumber = `${this.minecraftVersion}-${version}`;
|
|
171
|
+
const found = versions.find(v => v.version_number === version || v.version_number === canonicalVersionNumber);
|
|
172
|
+
if (!found) {
|
|
173
|
+
throw new Error(`IconExporter version "${version}" not found on Modrinth for Minecraft ${this.minecraftVersion} with NeoForge`);
|
|
174
|
+
}
|
|
175
|
+
matched = found;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Modrinth returns versions newest-first
|
|
179
|
+
matched = versions[0];
|
|
180
|
+
}
|
|
181
|
+
const primaryFile = matched.files.find(f => f.primary) || matched.files[0];
|
|
182
|
+
if (!primaryFile) {
|
|
183
|
+
throw new Error(`No files found for IconExporter version ${matched.version_number}`);
|
|
184
|
+
}
|
|
185
|
+
return { url: primaryFile.url, filename: primaryFile.filename };
|
|
186
|
+
});
|
|
187
|
+
}
|
|
107
188
|
/**
|
|
108
189
|
* Set up the game directory with mods and options.
|
|
109
190
|
*/
|
|
110
191
|
setupGameDirectory() {
|
|
111
192
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
|
-
const gameDir = (0,
|
|
113
|
-
const modsDir = (0,
|
|
193
|
+
const gameDir = (0, node_path_1.join)(this.workDir, IconsGenerator.hmcGameSubdir);
|
|
194
|
+
const modsDir = (0, node_path_1.join)(gameDir, 'mods');
|
|
114
195
|
if (!fs.existsSync(modsDir)) {
|
|
115
196
|
yield fs.promises.mkdir(modsDir, { recursive: true });
|
|
116
197
|
}
|
|
@@ -120,7 +201,7 @@ class IconsGenerator {
|
|
|
120
201
|
let copied = 0;
|
|
121
202
|
for (const mod of mods) {
|
|
122
203
|
if (mod.endsWith('.jar')) {
|
|
123
|
-
yield fs.promises.copyFile((0,
|
|
204
|
+
yield fs.promises.copyFile((0, node_path_1.join)(this.modsDir, mod), (0, node_path_1.join)(modsDir, mod));
|
|
124
205
|
copied++;
|
|
125
206
|
}
|
|
126
207
|
}
|
|
@@ -130,20 +211,20 @@ class IconsGenerator {
|
|
|
130
211
|
process.stdout.write(`Warning: mods directory not found: ${this.modsDir}\n`);
|
|
131
212
|
}
|
|
132
213
|
// Copy IconExporter to mods directory
|
|
133
|
-
const iconExporterSrc = (0,
|
|
214
|
+
const iconExporterSrc = (0, node_path_1.join)(this.workDir, IconsGenerator.iconExporterJar);
|
|
134
215
|
if (fs.existsSync(iconExporterSrc)) {
|
|
135
|
-
yield fs.promises.copyFile(iconExporterSrc, (0,
|
|
216
|
+
yield fs.promises.copyFile(iconExporterSrc, (0, node_path_1.join)(modsDir, IconsGenerator.iconExporterJar));
|
|
136
217
|
process.stdout.write('Added IconExporter to mods directory\n');
|
|
137
218
|
}
|
|
138
219
|
// Download hmc-specifics if not already present (avoids relying on HeadlessMC's
|
|
139
220
|
// GitHub API lookup which may hit rate limits in CI environments)
|
|
140
221
|
const existingMods = yield fs.promises.readdir(modsDir);
|
|
141
|
-
const hasHmcSpecifics = existingMods.some(
|
|
222
|
+
const hasHmcSpecifics = existingMods.some(f => f.startsWith('hmc-specifics-'));
|
|
142
223
|
if (!hasHmcSpecifics) {
|
|
143
224
|
yield this.downloadHmcSpecifics(modsDir);
|
|
144
225
|
}
|
|
145
226
|
// Write options.txt to disable accessibility screen and pauseOnLostFocus
|
|
146
|
-
const optionsPath = (0,
|
|
227
|
+
const optionsPath = (0, node_path_1.join)(gameDir, 'options.txt');
|
|
147
228
|
if (!fs.existsSync(optionsPath)) {
|
|
148
229
|
yield fs.promises.writeFile(optionsPath, 'pauseOnLostFocus:false\nonboardAccessibility:false\n');
|
|
149
230
|
}
|
|
@@ -153,12 +234,12 @@ class IconsGenerator {
|
|
|
153
234
|
* Write the HeadlessMC configuration file.
|
|
154
235
|
*/
|
|
155
236
|
writeHmcConfig() {
|
|
156
|
-
const configDir = (0,
|
|
237
|
+
const configDir = (0, node_path_1.join)(this.workDir, IconsGenerator.hmcConfigSubdir);
|
|
157
238
|
if (!fs.existsSync(configDir)) {
|
|
158
239
|
fs.mkdirSync(configDir, { recursive: true });
|
|
159
240
|
}
|
|
160
|
-
const gameDir = (0,
|
|
161
|
-
const configPath = (0,
|
|
241
|
+
const gameDir = (0, node_path_1.join)(this.workDir, IconsGenerator.hmcGameSubdir);
|
|
242
|
+
const configPath = (0, node_path_1.join)(configDir, 'config.properties');
|
|
162
243
|
const config = [
|
|
163
244
|
`hmc.mcdir=${gameDir}`,
|
|
164
245
|
'hmc.jline.enabled=false',
|
|
@@ -171,19 +252,23 @@ class IconsGenerator {
|
|
|
171
252
|
*/
|
|
172
253
|
runGameAndExportIcons() {
|
|
173
254
|
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
-
const jarPath = (0,
|
|
255
|
+
const jarPath = (0, node_path_1.join)(this.workDir, IconsGenerator.headlessMcJar);
|
|
175
256
|
return new Promise((resolve, reject) => {
|
|
176
|
-
const proc = (0,
|
|
177
|
-
`-Dhmc.mcdir=${(0,
|
|
257
|
+
const proc = (0, node_child_process_1.spawn)('java', [
|
|
258
|
+
`-Dhmc.mcdir=${(0, node_path_1.join)(this.workDir, IconsGenerator.hmcGameSubdir)}`,
|
|
178
259
|
// Tell HeadlessMC that Xvfb is in use so it skips offline headless checks
|
|
179
260
|
'-Dhmc.check.xvfb=true',
|
|
180
|
-
'-jar',
|
|
261
|
+
'-jar',
|
|
262
|
+
jarPath,
|
|
181
263
|
], {
|
|
182
264
|
cwd: this.workDir,
|
|
183
265
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
184
266
|
env: Object.assign(Object.assign({}, process.env), {
|
|
185
267
|
// Disable ANSI escape sequences for easier parsing
|
|
186
|
-
|
|
268
|
+
// eslint-disable-next-line ts/naming-convention
|
|
269
|
+
TERM: 'dumb',
|
|
270
|
+
// eslint-disable-next-line ts/naming-convention
|
|
271
|
+
NO_COLOR: '1' }),
|
|
187
272
|
});
|
|
188
273
|
let outputBuffer = '';
|
|
189
274
|
// Per-state buffer: cleared on every state transition, so checks only match
|
|
@@ -196,7 +281,7 @@ class IconsGenerator {
|
|
|
196
281
|
let commandSent = false;
|
|
197
282
|
const sendCommand = (command) => {
|
|
198
283
|
process.stdout.write(`[HMC] > ${command}\n`);
|
|
199
|
-
proc.stdin.write(command
|
|
284
|
+
proc.stdin.write(`${command}\n`);
|
|
200
285
|
};
|
|
201
286
|
// Click a button by its text label, resolving to numeric ID from the last gui output.
|
|
202
287
|
// HeadlessMC's click command requires the numeric id, not button text.
|
|
@@ -206,13 +291,13 @@ class IconsGenerator {
|
|
|
206
291
|
const lastScreenIdx = outputBuffer.lastIndexOf('\nScreen:');
|
|
207
292
|
const recentGuiOutput = lastScreenIdx >= 0 ? outputBuffer.slice(lastScreenIdx) : outputBuffer;
|
|
208
293
|
const id = this.findButtonIdByText(recentGuiOutput, buttonText);
|
|
209
|
-
if (id
|
|
210
|
-
sendCommand(`click ${id}`);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
294
|
+
if (id === null) {
|
|
213
295
|
process.stdout.write(`[HMC] Warning: button "${buttonText}" not found in gui output, trying text fallback\n`);
|
|
214
296
|
sendCommand(`click ${buttonText}`);
|
|
215
297
|
}
|
|
298
|
+
else {
|
|
299
|
+
sendCommand(`click ${id}`);
|
|
300
|
+
}
|
|
216
301
|
};
|
|
217
302
|
const transitionTo = (newState) => {
|
|
218
303
|
if (state !== newState) {
|
|
@@ -220,7 +305,8 @@ class IconsGenerator {
|
|
|
220
305
|
state = newState;
|
|
221
306
|
stateSettledAt = Date.now();
|
|
222
307
|
commandSent = false;
|
|
223
|
-
|
|
308
|
+
// Reset per-state buffer on every transition
|
|
309
|
+
stateBuffer = '';
|
|
224
310
|
}
|
|
225
311
|
};
|
|
226
312
|
const handleOutput = (chunk) => {
|
|
@@ -228,10 +314,10 @@ class IconsGenerator {
|
|
|
228
314
|
stateBuffer += chunk;
|
|
229
315
|
// Keep global buffer size manageable
|
|
230
316
|
if (outputBuffer.length > 100000) {
|
|
231
|
-
outputBuffer = outputBuffer.slice(
|
|
317
|
+
outputBuffer = outputBuffer.slice(-20000);
|
|
232
318
|
}
|
|
233
319
|
// Track the current screen from each gui output chunk
|
|
234
|
-
const screenMatch =
|
|
320
|
+
const screenMatch = /^Screen:\s*(.+)$/mu.exec(chunk);
|
|
235
321
|
if (screenMatch) {
|
|
236
322
|
lastKnownScreen = screenMatch[1].trim();
|
|
237
323
|
process.stdout.write(`[HMC] Current screen: ${lastKnownScreen}\n`);
|
|
@@ -296,8 +382,8 @@ class IconsGenerator {
|
|
|
296
382
|
clickByText('Create New World');
|
|
297
383
|
transitionTo('creating_world');
|
|
298
384
|
}
|
|
299
|
-
else if (stateBuffer.includes(
|
|
300
|
-
//
|
|
385
|
+
else if (stateBuffer.includes('Couldn\'t find command for \'[gui]\'') && !commandSent) {
|
|
386
|
+
// Gui not yet recognized; hmc-specifics may still be initializing - retry
|
|
301
387
|
commandSent = true;
|
|
302
388
|
setTimeout(() => {
|
|
303
389
|
sendCommand('gui');
|
|
@@ -325,11 +411,12 @@ class IconsGenerator {
|
|
|
325
411
|
clickByText('Create New World');
|
|
326
412
|
transitionTo('creating_world');
|
|
327
413
|
}
|
|
328
|
-
else if ((stateBuffer.includes('logged in') || stateBuffer.includes('not displaying a Gui')) &&
|
|
414
|
+
else if ((stateBuffer.includes('logged in') || stateBuffer.includes('not displaying a Gui')) &&
|
|
415
|
+
!commandSent) {
|
|
329
416
|
// World already loaded (detected either from HMC login message or from a gui poll)
|
|
330
417
|
commandSent = true;
|
|
331
418
|
setTimeout(() => {
|
|
332
|
-
sendCommand(`/iconexporter export ${IconsGenerator.
|
|
419
|
+
sendCommand(`/iconexporter export ${IconsGenerator.defaultIconSize}`);
|
|
333
420
|
transitionTo('exporting_icons');
|
|
334
421
|
}, 3000);
|
|
335
422
|
}
|
|
@@ -367,7 +454,7 @@ class IconsGenerator {
|
|
|
367
454
|
commandSent = true;
|
|
368
455
|
// Wait for world to fully initialize before triggering export
|
|
369
456
|
setTimeout(() => {
|
|
370
|
-
sendCommand(`/iconexporter export ${IconsGenerator.
|
|
457
|
+
sendCommand(`/iconexporter export ${IconsGenerator.defaultIconSize}`);
|
|
371
458
|
transitionTo('exporting_icons');
|
|
372
459
|
}, 3000);
|
|
373
460
|
}
|
|
@@ -383,7 +470,7 @@ class IconsGenerator {
|
|
|
383
470
|
else if (Date.now() - stateSettledAt > 120000 && !commandSent) {
|
|
384
471
|
// Timeout waiting for world - try to proceed anyway
|
|
385
472
|
commandSent = true;
|
|
386
|
-
sendCommand(`/iconexporter export ${IconsGenerator.
|
|
473
|
+
sendCommand(`/iconexporter export ${IconsGenerator.defaultIconSize}`);
|
|
387
474
|
transitionTo('exporting_icons');
|
|
388
475
|
}
|
|
389
476
|
else if (!commandSent) {
|
|
@@ -412,8 +499,9 @@ class IconsGenerator {
|
|
|
412
499
|
sendCommand('quit');
|
|
413
500
|
transitionTo('quitting');
|
|
414
501
|
}, 5000);
|
|
502
|
+
// 20 minutes
|
|
415
503
|
}
|
|
416
|
-
else if (Date.now() - stateSettledAt > 1200000) {
|
|
504
|
+
else if (Date.now() - stateSettledAt > 1200000) {
|
|
417
505
|
// Timeout - quit anyway
|
|
418
506
|
commandSent = true;
|
|
419
507
|
process.stdout.write('[HMC] Warning: icon export may not have completed, quitting...\n');
|
|
@@ -473,7 +561,7 @@ class IconsGenerator {
|
|
|
473
561
|
*/
|
|
474
562
|
copyIcons() {
|
|
475
563
|
return __awaiter(this, void 0, void 0, function* () {
|
|
476
|
-
const exportDir = (0,
|
|
564
|
+
const exportDir = (0, node_path_1.join)(this.workDir, IconsGenerator.hmcGameSubdir, IconsGenerator.iconExportSubdir);
|
|
477
565
|
if (!fs.existsSync(exportDir)) {
|
|
478
566
|
throw new Error(`Icon export directory not found: ${exportDir}. Make sure the IconExporter command ran successfully.`);
|
|
479
567
|
}
|
|
@@ -481,9 +569,9 @@ class IconsGenerator {
|
|
|
481
569
|
yield fs.promises.mkdir(this.iconsDir, { recursive: true });
|
|
482
570
|
}
|
|
483
571
|
const files = yield fs.promises.readdir(exportDir);
|
|
484
|
-
const pngFiles = files.filter(
|
|
572
|
+
const pngFiles = files.filter(f => f.endsWith('.png'));
|
|
485
573
|
for (const file of pngFiles) {
|
|
486
|
-
yield fs.promises.copyFile((0,
|
|
574
|
+
yield fs.promises.copyFile((0, node_path_1.join)(exportDir, file), (0, node_path_1.join)(this.iconsDir, file));
|
|
487
575
|
}
|
|
488
576
|
process.stdout.write(`Copied ${pngFiles.length} icons to ${this.iconsDir}\n`);
|
|
489
577
|
});
|
|
@@ -499,9 +587,9 @@ class IconsGenerator {
|
|
|
499
587
|
*/
|
|
500
588
|
findButtonIdByText(guiOutput, buttonText) {
|
|
501
589
|
for (const line of guiOutput.split('\n')) {
|
|
502
|
-
const cols = line.trim().split(/\s{2,}/);
|
|
503
|
-
if (cols.length >= 2 && /^\d
|
|
504
|
-
return parseInt(cols[0], 10);
|
|
590
|
+
const cols = line.trim().split(/\s{2,}/u);
|
|
591
|
+
if (cols.length >= 2 && /^\d+$/u.test(cols[0]) && cols[1].trim() === buttonText) {
|
|
592
|
+
return Number.parseInt(cols[0], 10);
|
|
505
593
|
}
|
|
506
594
|
}
|
|
507
595
|
return null;
|
|
@@ -539,21 +627,17 @@ class IconsGenerator {
|
|
|
539
627
|
downloadHmcSpecifics(modsDir) {
|
|
540
628
|
return __awaiter(this, void 0, void 0, function* () {
|
|
541
629
|
process.stdout.write('Downloading hmc-specifics...\n');
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
headers.Authorization = `token ${this.githubToken}`;
|
|
545
|
-
}
|
|
546
|
-
const apiUrl = `https://api.github.com/repos/${IconsGenerator.HMC_SPECIFICS_REPO}/releases/latest`;
|
|
547
|
-
const apiResponse = yield (0, node_fetch_1.default)(apiUrl, { headers });
|
|
630
|
+
const apiUrl = `https://api.github.com/repos/${IconsGenerator.hmcSpecificsRepo}/releases/latest`;
|
|
631
|
+
const apiResponse = yield (0, node_fetch_1.default)(apiUrl);
|
|
548
632
|
if (!apiResponse.ok) {
|
|
549
633
|
throw new Error(`Failed to fetch hmc-specifics release info: ${apiResponse.status} ${apiResponse.statusText}`);
|
|
550
634
|
}
|
|
551
635
|
const release = yield apiResponse.json();
|
|
552
|
-
const asset = release.assets.find(
|
|
636
|
+
const asset = release.assets.find(a => a.name.includes(this.minecraftVersion) && a.name.includes('neoforge'));
|
|
553
637
|
if (!asset) {
|
|
554
638
|
throw new Error(`Could not find hmc-specifics asset for MC ${this.minecraftVersion} in release ${release.tag_name}`);
|
|
555
639
|
}
|
|
556
|
-
const destPath = (0,
|
|
640
|
+
const destPath = (0, node_path_1.join)(modsDir, asset.name);
|
|
557
641
|
yield this.downloadFile(asset.browser_download_url, destPath);
|
|
558
642
|
process.stdout.write(`Downloaded hmc-specifics to ${destPath}\n`);
|
|
559
643
|
});
|
|
@@ -571,7 +655,7 @@ class IconsGenerator {
|
|
|
571
655
|
if (!response.ok) {
|
|
572
656
|
throw new Error(`Failed to download from ${url}: ${response.status} ${response.statusText}`);
|
|
573
657
|
}
|
|
574
|
-
const parentDir = (0,
|
|
658
|
+
const parentDir = (0, node_path_1.join)(destPath, '..');
|
|
575
659
|
if (!fs.existsSync(parentDir)) {
|
|
576
660
|
yield fs.promises.mkdir(parentDir, { recursive: true });
|
|
577
661
|
}
|
|
@@ -587,11 +671,13 @@ class IconsGenerator {
|
|
|
587
671
|
}
|
|
588
672
|
}
|
|
589
673
|
exports.IconsGenerator = IconsGenerator;
|
|
590
|
-
IconsGenerator.
|
|
591
|
-
IconsGenerator.
|
|
592
|
-
IconsGenerator.
|
|
593
|
-
IconsGenerator.
|
|
594
|
-
IconsGenerator.
|
|
595
|
-
IconsGenerator.
|
|
596
|
-
IconsGenerator.
|
|
674
|
+
IconsGenerator.headlessMcJar = 'headlessmc-launcher.jar';
|
|
675
|
+
IconsGenerator.iconExporterJar = 'iconexporter.jar';
|
|
676
|
+
IconsGenerator.hmcGameSubdir = 'game';
|
|
677
|
+
IconsGenerator.hmcConfigSubdir = 'HeadlessMC';
|
|
678
|
+
IconsGenerator.iconExportSubdir = 'icon-exports-x64';
|
|
679
|
+
IconsGenerator.defaultIconSize = 64;
|
|
680
|
+
IconsGenerator.hmcSpecificsRepo = 'headlesshq/hmc-specifics';
|
|
681
|
+
IconsGenerator.modrinthApiBase = 'https://api.modrinth.com/v2';
|
|
682
|
+
IconsGenerator.iconExporterModrinthSlug = 'icon-exporter';
|
|
597
683
|
//# sourceMappingURL=IconsGenerator.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Readable } from
|
|
2
|
-
import { ISerializeContext } from
|
|
3
|
-
import { IFileWriter } from
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { ISerializeContext } from '../serialize/HtmlInfoBookSerializer';
|
|
3
|
+
import type { IFileWriter } from './IFileWriter';
|
|
4
4
|
/**
|
|
5
5
|
* A context-based {@link IFileWriter}.
|
|
6
6
|
*/
|