@edgemodders/edgedeck-mcp 0.28.0 → 0.29.1
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/bin/edgedeck.mjs +1 -1
- package/dist/core.js +319 -128
- package/dist/mcp.js +57 -27
- package/package.json +1 -1
package/bin/edgedeck.mjs
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// edgedeck mcp serve the same verbs as MCP tools over stdio
|
|
24
24
|
//
|
|
25
25
|
// Options: --library <folder> (default %USERPROFILE%\EDGEdeck), --game <folder> (default: the
|
|
26
|
-
// library's remembered game, else the Steam
|
|
26
|
+
// library's remembered game, else the game found through Steam, GOG or Epic), --json.
|
|
27
27
|
|
|
28
28
|
import { createRequire } from "node:module";
|
|
29
29
|
import * as fs from "node:fs";
|
package/dist/core.js
CHANGED
|
@@ -1250,8 +1250,8 @@ var require_semaphore = __commonJS({
|
|
|
1250
1250
|
this._waiting = [];
|
|
1251
1251
|
}
|
|
1252
1252
|
lock(thunk) {
|
|
1253
|
-
return new Promise((
|
|
1254
|
-
this._waiting.push({ thunk, resolve:
|
|
1253
|
+
return new Promise((resolve5, reject) => {
|
|
1254
|
+
this._waiting.push({ thunk, resolve: resolve5, reject });
|
|
1255
1255
|
this.runNext();
|
|
1256
1256
|
});
|
|
1257
1257
|
}
|
|
@@ -2877,9 +2877,9 @@ ${JSON.stringify(message, null, 4)}`);
|
|
|
2877
2877
|
if (typeof cancellationStrategy.sender.enableCancellation === "function") {
|
|
2878
2878
|
cancellationStrategy.sender.enableCancellation(requestMessage);
|
|
2879
2879
|
}
|
|
2880
|
-
return new Promise(async (
|
|
2880
|
+
return new Promise(async (resolve5, reject) => {
|
|
2881
2881
|
const resolveWithCleanup = (r) => {
|
|
2882
|
-
|
|
2882
|
+
resolve5(r);
|
|
2883
2883
|
cancellationStrategy.sender.cleanup(id);
|
|
2884
2884
|
disposable?.dispose();
|
|
2885
2885
|
};
|
|
@@ -3308,10 +3308,10 @@ var require_ril = __commonJS({
|
|
|
3308
3308
|
return api_1.Disposable.create(() => this.stream.off("end", listener));
|
|
3309
3309
|
}
|
|
3310
3310
|
write(data, encoding) {
|
|
3311
|
-
return new Promise((
|
|
3311
|
+
return new Promise((resolve5, reject) => {
|
|
3312
3312
|
const callback = (error) => {
|
|
3313
3313
|
if (error === void 0 || error === null) {
|
|
3314
|
-
|
|
3314
|
+
resolve5();
|
|
3315
3315
|
} else {
|
|
3316
3316
|
reject(error);
|
|
3317
3317
|
}
|
|
@@ -3447,9 +3447,9 @@ var require_main = __commonJS({
|
|
|
3447
3447
|
exports2.createMessageConnection = createMessageConnection2;
|
|
3448
3448
|
var ril_1 = __importDefault(require_ril());
|
|
3449
3449
|
ril_1.default.install();
|
|
3450
|
-
var
|
|
3450
|
+
var path15 = __importStar(require("path"));
|
|
3451
3451
|
var os4 = __importStar(require("os"));
|
|
3452
|
-
var
|
|
3452
|
+
var fs16 = __importStar(require("fs"));
|
|
3453
3453
|
var crypto_1 = require("crypto");
|
|
3454
3454
|
var net_1 = require("net");
|
|
3455
3455
|
var api_1 = require_api();
|
|
@@ -3590,7 +3590,7 @@ var require_main = __commonJS({
|
|
|
3590
3590
|
}
|
|
3591
3591
|
let randomLength = 32;
|
|
3592
3592
|
const fixedLength = "/lsp-.sock".length;
|
|
3593
|
-
const tmpDir =
|
|
3593
|
+
const tmpDir = fs16.realpathSync(XDG_RUNTIME_DIR ?? os4.tmpdir());
|
|
3594
3594
|
const limit = safeIpcPathLengths.get(process.platform);
|
|
3595
3595
|
if (limit !== void 0) {
|
|
3596
3596
|
randomLength = Math.min(limit - tmpDir.length - fixedLength, randomLength);
|
|
@@ -3599,14 +3599,14 @@ var require_main = __commonJS({
|
|
|
3599
3599
|
throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
|
|
3600
3600
|
}
|
|
3601
3601
|
const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
|
|
3602
|
-
return
|
|
3602
|
+
return path15.join(tmpDir, `lsp-${randomSuffix}.sock`);
|
|
3603
3603
|
}
|
|
3604
3604
|
function createClientPipeTransport(pipeName, encoding = "utf-8") {
|
|
3605
3605
|
let connectResolve;
|
|
3606
|
-
const connected = new Promise((
|
|
3607
|
-
connectResolve =
|
|
3606
|
+
const connected = new Promise((resolve5, _reject) => {
|
|
3607
|
+
connectResolve = resolve5;
|
|
3608
3608
|
});
|
|
3609
|
-
return new Promise((
|
|
3609
|
+
return new Promise((resolve5, reject) => {
|
|
3610
3610
|
const server = (0, net_1.createServer)((socket) => {
|
|
3611
3611
|
server.close();
|
|
3612
3612
|
connectResolve([
|
|
@@ -3617,7 +3617,7 @@ var require_main = __commonJS({
|
|
|
3617
3617
|
server.on("error", reject);
|
|
3618
3618
|
server.listen(pipeName, () => {
|
|
3619
3619
|
server.removeListener("error", reject);
|
|
3620
|
-
|
|
3620
|
+
resolve5({
|
|
3621
3621
|
onConnected: () => {
|
|
3622
3622
|
return connected;
|
|
3623
3623
|
}
|
|
@@ -3634,10 +3634,10 @@ var require_main = __commonJS({
|
|
|
3634
3634
|
}
|
|
3635
3635
|
function createClientSocketTransport(port, encoding = "utf-8") {
|
|
3636
3636
|
let connectResolve;
|
|
3637
|
-
const connected = new Promise((
|
|
3638
|
-
connectResolve =
|
|
3637
|
+
const connected = new Promise((resolve5, _reject) => {
|
|
3638
|
+
connectResolve = resolve5;
|
|
3639
3639
|
});
|
|
3640
|
-
return new Promise((
|
|
3640
|
+
return new Promise((resolve5, reject) => {
|
|
3641
3641
|
const server = (0, net_1.createServer)((socket) => {
|
|
3642
3642
|
server.close();
|
|
3643
3643
|
connectResolve([
|
|
@@ -3654,7 +3654,7 @@ var require_main = __commonJS({
|
|
|
3654
3654
|
return;
|
|
3655
3655
|
}
|
|
3656
3656
|
const boundPort = address.port;
|
|
3657
|
-
|
|
3657
|
+
resolve5({
|
|
3658
3658
|
port: () => boundPort,
|
|
3659
3659
|
onConnected: () => {
|
|
3660
3660
|
return connected;
|
|
@@ -3700,6 +3700,7 @@ __export(core_exports, {
|
|
|
3700
3700
|
BridgeClient: () => BridgeClient,
|
|
3701
3701
|
BridgeRepo: () => BridgeRepo,
|
|
3702
3702
|
DefaultLoadout: () => DefaultLoadout,
|
|
3703
|
+
FrameworkSources: () => FrameworkSources,
|
|
3703
3704
|
FrameworksSection: () => FrameworksSection,
|
|
3704
3705
|
KnownModNames: () => KnownModNames,
|
|
3705
3706
|
ManifestName: () => ManifestName,
|
|
@@ -3737,6 +3738,7 @@ __export(core_exports, {
|
|
|
3737
3738
|
deleteLoadout: () => deleteLoadout,
|
|
3738
3739
|
deleteSection: () => deleteSection,
|
|
3739
3740
|
deploy: () => deploy,
|
|
3741
|
+
deployMethod: () => deployMethod,
|
|
3740
3742
|
deployRedmod: () => deployRedmod,
|
|
3741
3743
|
deployReportsFolder: () => deployReportsFolder,
|
|
3742
3744
|
deployedRedmodPackages: () => deployedRedmodPackages,
|
|
@@ -3768,12 +3770,15 @@ __export(core_exports, {
|
|
|
3768
3770
|
formatBytes: () => formatBytes,
|
|
3769
3771
|
forwarderCommand: () => forwarderCommand,
|
|
3770
3772
|
frameworkDetail: () => frameworkDetail,
|
|
3773
|
+
frameworkFamily: () => frameworkFamily,
|
|
3774
|
+
frameworkStates: () => frameworkStates,
|
|
3771
3775
|
frameworkSummary: () => frameworkSummary,
|
|
3772
3776
|
gameExe: () => gameExe,
|
|
3773
3777
|
gameRunning: () => gameRunning,
|
|
3774
3778
|
gameVersion: () => gameVersion,
|
|
3775
3779
|
guessName: () => guessName,
|
|
3776
3780
|
hasWebSocket: () => hasWebSocket,
|
|
3781
|
+
holdsLibrary: () => holdsLibrary,
|
|
3777
3782
|
idFor: () => idFor,
|
|
3778
3783
|
importFromVortex: () => importFromVortex,
|
|
3779
3784
|
importLoadout: () => importLoadout,
|
|
@@ -3785,6 +3790,7 @@ __export(core_exports, {
|
|
|
3785
3790
|
isGameFolder: () => isGameFolder,
|
|
3786
3791
|
keepDownload: () => keepDownload,
|
|
3787
3792
|
latestBridgeRelease: () => latestBridgeRelease,
|
|
3793
|
+
latestFrameworkRelease: () => latestFrameworkRelease,
|
|
3788
3794
|
launchTarget: () => launchTarget,
|
|
3789
3795
|
libraryFolder: () => libraryFolder,
|
|
3790
3796
|
librarySize: () => librarySize,
|
|
@@ -3797,6 +3803,7 @@ __export(core_exports, {
|
|
|
3797
3803
|
lostFiles: () => lostFiles,
|
|
3798
3804
|
makeActive: () => makeActive,
|
|
3799
3805
|
md5Of: () => md5Of,
|
|
3806
|
+
missingFrameworks: () => missingFrameworks,
|
|
3800
3807
|
modDeployPaths: () => modDeployPaths,
|
|
3801
3808
|
modFolder: () => modFolder,
|
|
3802
3809
|
modPageUrl: () => modPageUrl,
|
|
@@ -3805,8 +3812,10 @@ __export(core_exports, {
|
|
|
3805
3812
|
modsRemoved: () => modsRemoved,
|
|
3806
3813
|
modsTouched: () => modsTouched,
|
|
3807
3814
|
moveInSection: () => moveInSection,
|
|
3815
|
+
moveLibrary: () => moveLibrary,
|
|
3808
3816
|
moveMod: () => moveMod,
|
|
3809
3817
|
moveSection: () => moveSection,
|
|
3818
|
+
moveTarget: () => moveTarget,
|
|
3810
3819
|
needsRefresh: () => needsRefresh,
|
|
3811
3820
|
newLoadout: () => newLoadout,
|
|
3812
3821
|
newestFile: () => newestFile,
|
|
@@ -3833,9 +3842,11 @@ __export(core_exports, {
|
|
|
3833
3842
|
registerNxmHandler: () => registerNxmHandler,
|
|
3834
3843
|
removeMod: () => removeMod,
|
|
3835
3844
|
removeOlderBridges: () => removeOlderBridges,
|
|
3845
|
+
removeTemp: () => removeTemp,
|
|
3836
3846
|
renameFamily: () => renameFamily,
|
|
3837
3847
|
renameLoadout: () => renameLoadout,
|
|
3838
3848
|
renameSection: () => renameSection,
|
|
3849
|
+
sameDrive: () => sameDrive,
|
|
3839
3850
|
sameNameMod: () => sameNameMod,
|
|
3840
3851
|
saveLibrary: () => saveLibrary,
|
|
3841
3852
|
saveLoadout: () => saveLoadout,
|
|
@@ -3848,6 +3859,7 @@ __export(core_exports, {
|
|
|
3848
3859
|
settleNewVersion: () => settleNewVersion,
|
|
3849
3860
|
signIn: () => signIn,
|
|
3850
3861
|
signInWithOAuth: () => signInWithOAuth,
|
|
3862
|
+
singleRednodeApp: () => singleRednodeApp,
|
|
3851
3863
|
snapshot: () => snapshot,
|
|
3852
3864
|
sourcePath: () => sourcePath,
|
|
3853
3865
|
ssoPageUrl: () => ssoPageUrl,
|
|
@@ -3993,6 +4005,14 @@ function findRarTool() {
|
|
|
3993
4005
|
if (unrar) return { exe: unrar, args: (file, dest) => ["x", "-y", "-o+", file, dest + path2.sep] };
|
|
3994
4006
|
return void 0;
|
|
3995
4007
|
}
|
|
4008
|
+
function removeTemp(dir) {
|
|
4009
|
+
try {
|
|
4010
|
+
fs2.rmSync(dir, { recursive: true, force: true, maxRetries: 6, retryDelay: 250 });
|
|
4011
|
+
return void 0;
|
|
4012
|
+
} catch (err) {
|
|
4013
|
+
return `could not remove the temporary folder ${dir} (${err instanceof Error ? err.message : String(err)}); Windows clears its temp folder in time`;
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3996
4016
|
async function unpackOrCopy(file, dest, sevenZa) {
|
|
3997
4017
|
if (isArchiveFile(file)) return extractArchive(file, dest, sevenZa);
|
|
3998
4018
|
fs2.mkdirSync(dest, { recursive: true });
|
|
@@ -4039,8 +4059,8 @@ var NexusClient = class {
|
|
|
4039
4059
|
get usesToken() {
|
|
4040
4060
|
return "bearer" in this.auth;
|
|
4041
4061
|
}
|
|
4042
|
-
async get(
|
|
4043
|
-
const res = await fetch(`${this.options.api ?? NexusApi}${
|
|
4062
|
+
async get(path15, retried = false) {
|
|
4063
|
+
const res = await fetch(`${this.options.api ?? NexusApi}${path15}`, {
|
|
4044
4064
|
headers: {
|
|
4045
4065
|
..."bearer" in this.auth ? { authorization: `Bearer ${this.auth.bearer}` } : { apikey: this.auth.apiKey },
|
|
4046
4066
|
accept: "application/json",
|
|
@@ -4057,7 +4077,7 @@ var NexusClient = class {
|
|
|
4057
4077
|
const bearer = await this.options.refresh();
|
|
4058
4078
|
if (bearer) {
|
|
4059
4079
|
this.auth = { bearer };
|
|
4060
|
-
return this.get(
|
|
4080
|
+
return this.get(path15, true);
|
|
4061
4081
|
}
|
|
4062
4082
|
}
|
|
4063
4083
|
if (!res.ok) {
|
|
@@ -4163,15 +4183,15 @@ async function installDotnetRuntime(log) {
|
|
|
4163
4183
|
try {
|
|
4164
4184
|
await download(DotnetInstallScript, script);
|
|
4165
4185
|
log(`Installing the .NET 10 runtime into ${root}`);
|
|
4166
|
-
await new Promise((
|
|
4186
|
+
await new Promise((resolve5, reject) => {
|
|
4167
4187
|
(0, import_node_child_process3.execFile)("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script, "-Channel", "10.0", "-Runtime", "dotnet", "-InstallDir", root, "-NoPath"], { windowsHide: true, maxBuffer: 1 << 24 }, (err, stdout, stderr) => {
|
|
4168
4188
|
for (const line of `${stdout}
|
|
4169
4189
|
${stderr}`.split(/\r?\n/)) if (line.trim()) log(` ${line.trim()}`);
|
|
4170
|
-
err ? reject(new Error(`dotnet-install failed: ${err.message}`)) :
|
|
4190
|
+
err ? reject(new Error(`dotnet-install failed: ${err.message}`)) : resolve5();
|
|
4171
4191
|
});
|
|
4172
4192
|
});
|
|
4173
4193
|
} finally {
|
|
4174
|
-
|
|
4194
|
+
removeTemp(temp);
|
|
4175
4195
|
}
|
|
4176
4196
|
if (!hasNet10(root)) throw new Error("The .NET 10 runtime did not install. See the log.");
|
|
4177
4197
|
return root;
|
|
@@ -4221,7 +4241,7 @@ async function installBridge(release, storageDir, sevenZa, progress, log) {
|
|
|
4221
4241
|
fs4.mkdirSync(target, { recursive: true });
|
|
4222
4242
|
await extractArchive(zip, target, sevenZa);
|
|
4223
4243
|
} finally {
|
|
4224
|
-
|
|
4244
|
+
removeTemp(temp);
|
|
4225
4245
|
}
|
|
4226
4246
|
if (!fs4.existsSync(exe)) throw new Error("The zip did not contain REDbridge.exe.");
|
|
4227
4247
|
log(`REDbridge ${release.version} installed at ${exe}`);
|
|
@@ -4296,7 +4316,8 @@ function folderName(name) {
|
|
|
4296
4316
|
return name.replace(/[<>:"/\\|?*]+/g, "").trim() || "Mod";
|
|
4297
4317
|
}
|
|
4298
4318
|
function rednodeManifest(placements) {
|
|
4299
|
-
const
|
|
4319
|
+
const to = singleRednodeApp(placements.map((x) => x.to));
|
|
4320
|
+
const p = to === void 0 ? void 0 : placements.find((x) => x.to === to);
|
|
4300
4321
|
if (!p) return void 0;
|
|
4301
4322
|
try {
|
|
4302
4323
|
const m = JSON.parse(fs5.readFileSync(p.from, "utf8"));
|
|
@@ -4306,6 +4327,12 @@ function rednodeManifest(placements) {
|
|
|
4306
4327
|
return void 0;
|
|
4307
4328
|
}
|
|
4308
4329
|
}
|
|
4330
|
+
function singleRednodeApp(paths) {
|
|
4331
|
+
const manifests = paths.filter((f) => /^red4ext\/plugins\/REDnode\/apps\/[^/]+\/rednode-app\.json$/i.test(f));
|
|
4332
|
+
if (manifests.length !== 1) return void 0;
|
|
4333
|
+
if (paths.some((f) => /^red4ext\/plugins\/REDnode\//i.test(f) && !/^red4ext\/plugins\/REDnode\/apps\//i.test(f))) return void 0;
|
|
4334
|
+
return manifests[0];
|
|
4335
|
+
}
|
|
4309
4336
|
function rednodeId(manifest) {
|
|
4310
4337
|
try {
|
|
4311
4338
|
const m = JSON.parse(fs5.readFileSync(manifest, "utf8"));
|
|
@@ -4699,6 +4726,21 @@ function canHardlink(libraryRoot, gamePath) {
|
|
|
4699
4726
|
return false;
|
|
4700
4727
|
}
|
|
4701
4728
|
}
|
|
4729
|
+
var hardlinkable = /* @__PURE__ */ new Map();
|
|
4730
|
+
function deployMethod(libraryRoot, gamePath, current) {
|
|
4731
|
+
try {
|
|
4732
|
+
if (fs8.statSync(libraryRoot).dev !== fs8.statSync(gamePath).dev) return "copy";
|
|
4733
|
+
} catch {
|
|
4734
|
+
return current ?? "copy";
|
|
4735
|
+
}
|
|
4736
|
+
if (current === "link") return "link";
|
|
4737
|
+
const k = `${path7.resolve(libraryRoot).toLowerCase()}|${path7.resolve(gamePath).toLowerCase()}`;
|
|
4738
|
+
if (!hardlinkable.has(k)) hardlinkable.set(k, canHardlink(libraryRoot, gamePath));
|
|
4739
|
+
return hardlinkable.get(k) ? "link" : "copy";
|
|
4740
|
+
}
|
|
4741
|
+
function otherMethod(entry, method) {
|
|
4742
|
+
return method === "link" !== !!entry.ino;
|
|
4743
|
+
}
|
|
4702
4744
|
function sourceReplaced(entry, method, d) {
|
|
4703
4745
|
if (d.content !== void 0) return false;
|
|
4704
4746
|
const src = identity(d.src);
|
|
@@ -4706,10 +4748,10 @@ function sourceReplaced(entry, method, d) {
|
|
|
4706
4748
|
if (method === "link" && entry.ino) return src.ino !== entry.ino;
|
|
4707
4749
|
return src.size !== entry.size || Math.abs(src.mtimeMs - entry.mtimeMs) >= 2e3;
|
|
4708
4750
|
}
|
|
4709
|
-
function stillOurs(entry,
|
|
4751
|
+
function stillOurs(entry, _method, gamePath) {
|
|
4710
4752
|
const id = identity(gameFile(gamePath, entry.path));
|
|
4711
4753
|
if (!id) return "missing";
|
|
4712
|
-
if (
|
|
4754
|
+
if (entry.ino) {
|
|
4713
4755
|
if (id.ino !== entry.ino) return "changed";
|
|
4714
4756
|
if (id.nlink < 2 && entry.mod !== Generated) return "sourceMissing";
|
|
4715
4757
|
return "ok";
|
|
@@ -4725,7 +4767,7 @@ function generatedDiffers(file, content) {
|
|
|
4725
4767
|
}
|
|
4726
4768
|
function plan(lib, gamePath) {
|
|
4727
4769
|
const manifest = readManifest(gamePath);
|
|
4728
|
-
const method =
|
|
4770
|
+
const method = deployMethod(lib.root, gamePath, manifest?.method);
|
|
4729
4771
|
const desired = /* @__PURE__ */ new Map();
|
|
4730
4772
|
const conflicts = /* @__PURE__ */ new Map();
|
|
4731
4773
|
for (const mod of lib.mods) {
|
|
@@ -4756,6 +4798,7 @@ function plan(lib, gamePath) {
|
|
|
4756
4798
|
if (!e || (d.mod?.id ?? Generated) !== e.mod) return true;
|
|
4757
4799
|
if (d.content !== void 0 && generatedDiffers(gameFile(gamePath, d.rel), d.content)) return true;
|
|
4758
4800
|
if (stillOurs(e, method, gamePath) !== "ok") return true;
|
|
4801
|
+
if (d.content === void 0 && otherMethod(e, method)) return true;
|
|
4759
4802
|
return sourceReplaced(e, method, d);
|
|
4760
4803
|
});
|
|
4761
4804
|
return { method, desired: [...desired.values()], conflicts: [...conflicts.values()], stale, add, vortex: vortexManages(gamePath) };
|
|
@@ -4890,6 +4933,7 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
4890
4933
|
const p = plan(lib, gamePath);
|
|
4891
4934
|
const manifest = readManifest(gamePath) ?? { version: 1, library: lib.root, gamePath, method: p.method, time: "", files: [], dirs: [] };
|
|
4892
4935
|
manifest.library = lib.root;
|
|
4936
|
+
manifest.method = p.method;
|
|
4893
4937
|
const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0, lost: lostFiles(p) };
|
|
4894
4938
|
const entries = new Map(manifest.files.map((e) => [key(e.path), e]));
|
|
4895
4939
|
const scope = only ? modDeployPaths(gamePath, p, only) : void 0;
|
|
@@ -5016,6 +5060,7 @@ function pendingChanges(lib, gamePath, p) {
|
|
|
5016
5060
|
const st = stillOurs(e, p.method, gamePath);
|
|
5017
5061
|
if (st === "missing") out.push({ path: d.rel, mod: id, kind: "restore", detail: "missing from the game folder" });
|
|
5018
5062
|
else if (st === "changed") out.push({ path: d.rel, mod: id, kind: "restore", detail: "changed in the game folder; deploy asks whether to keep it" });
|
|
5063
|
+
else if (d.content === void 0 && otherMethod(e, p.method)) out.push({ path: d.rel, mod: id, kind: "relink", detail: p.method === "link" ? "the library is on the game's drive now: a hardlink instead of a copy" : "the library is on another drive now: a copy instead of a hardlink" });
|
|
5019
5064
|
else out.push({ path: d.rel, mod: id, kind: "relink", detail: d.content !== void 0 ? "the generated content changed (load order)" : "the library file was replaced (new version or rebuild)" });
|
|
5020
5065
|
}
|
|
5021
5066
|
}
|
|
@@ -5045,11 +5090,8 @@ function baseName(name) {
|
|
|
5045
5090
|
return name.toLowerCase().replace(/[_\-]+/g, " ").replace(/\bv?\d+(?:\.\d+)+[a-z0-9]*\b/g, " ").replace(/[^a-z0-9]+/g, " ").trim();
|
|
5046
5091
|
}
|
|
5047
5092
|
function rednodePackageId(mod) {
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
if (m) return m[1].toLowerCase();
|
|
5051
|
-
}
|
|
5052
|
-
return void 0;
|
|
5093
|
+
const manifest = singleRednodeApp(mod.files);
|
|
5094
|
+
return manifest ? /^red4ext\/plugins\/REDnode\/apps\/([^/]+)\//i.exec(manifest)[1].toLowerCase() : void 0;
|
|
5053
5095
|
}
|
|
5054
5096
|
function familyKey(mod) {
|
|
5055
5097
|
if (mod.family) return mod.family;
|
|
@@ -5192,28 +5234,165 @@ function frameworkDetail(c) {
|
|
|
5192
5234
|
return lines;
|
|
5193
5235
|
}
|
|
5194
5236
|
|
|
5195
|
-
// src/
|
|
5237
|
+
// src/frameworkGet.ts
|
|
5238
|
+
var FrameworkSources = [
|
|
5239
|
+
{ name: "RED4ext", nexusId: 2380, repo: "WopsS/RED4ext", asset: /^red4ext-[\d.]+\.zip$/i, signature: "red4ext/RED4ext.dll" },
|
|
5240
|
+
{ name: "Cyber Engine Tweaks", nexusId: 107, repo: "maximegmd/CyberEngineTweaks", asset: /^cet_[\d_]+\.zip$/i, signature: "bin/x64/plugins/cyber_engine_tweaks.asi" },
|
|
5241
|
+
{ name: "redscript", nexusId: 1511, repo: "jac3km4/redscript", asset: /^redscript-v?[\d.]+-windows\.zip$/i, signature: "engine/tools/scc.exe" },
|
|
5242
|
+
{ name: "ArchiveXL", nexusId: 4198, repo: "psiberx/cp2077-archive-xl", asset: /^ArchiveXL-[\d.]+\.zip$/i, signature: "red4ext/plugins/ArchiveXL/ArchiveXL.dll" },
|
|
5243
|
+
{ name: "TweakXL", nexusId: 4197, repo: "psiberx/cp2077-tweak-xl", asset: /^TweakXL-[\d.]+\.zip$/i, signature: "red4ext/plugins/TweakXL/TweakXL.dll" },
|
|
5244
|
+
{ name: "Codeware", nexusId: 7780, repo: "psiberx/cp2077-codeware", asset: /^Codeware-[\d.]+\.zip$/i, signature: "red4ext/plugins/Codeware/Codeware.dll" }
|
|
5245
|
+
];
|
|
5246
|
+
function frameworkFamily(lib, f) {
|
|
5247
|
+
const sig = f.signature.toLowerCase();
|
|
5248
|
+
return families(lib).find((fam) => fam.nexusId === f.nexusId || fam.mods.some((m) => m.files.some((x) => x.toLowerCase() === sig)));
|
|
5249
|
+
}
|
|
5250
|
+
function missingFrameworks(lib) {
|
|
5251
|
+
return FrameworkSources.filter((f) => !frameworkFamily(lib, f));
|
|
5252
|
+
}
|
|
5253
|
+
async function latestFrameworkRelease(f, fetchImpl = fetch, api = "https://api.github.com") {
|
|
5254
|
+
const headers = { Accept: "application/vnd.github+json", "User-Agent": "EDGEdeck" };
|
|
5255
|
+
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
5256
|
+
const res = await fetchImpl(`${api}/repos/${f.repo}/releases/latest`, { headers });
|
|
5257
|
+
if (!res.ok) throw new Error(res.status === 403 ? "GitHub's limit for calls without an account was reached; try again within the hour." : `GitHub answered ${res.status}.`);
|
|
5258
|
+
const rel = await res.json();
|
|
5259
|
+
const asset = rel.assets.find((a) => f.asset.test(a.name));
|
|
5260
|
+
if (!asset) throw new Error(`release ${rel.tag_name} has no file named like ${f.asset.source}.`);
|
|
5261
|
+
return { version: rel.tag_name.replace(/^v/i, ""), tag: rel.tag_name, assetName: asset.name, url: asset.browser_download_url, size: asset.size, page: rel.html_url };
|
|
5262
|
+
}
|
|
5263
|
+
async function frameworkStates(lib, fetchImpl, api) {
|
|
5264
|
+
return Promise.all(
|
|
5265
|
+
FrameworkSources.map(async (source) => {
|
|
5266
|
+
const family = frameworkFamily(lib, source);
|
|
5267
|
+
try {
|
|
5268
|
+
const release = await latestFrameworkRelease(source, fetchImpl, api);
|
|
5269
|
+
const newest2 = family?.mods[0]?.version ?? "";
|
|
5270
|
+
return { source, family, release, wanted: !family || !!newest2 && versionNewer(release.version, newest2) };
|
|
5271
|
+
} catch (err) {
|
|
5272
|
+
return { source, family, error: err instanceof Error ? err.message : String(err), wanted: !family };
|
|
5273
|
+
}
|
|
5274
|
+
})
|
|
5275
|
+
);
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5278
|
+
// src/libraryMove.ts
|
|
5196
5279
|
var fs9 = __toESM(require("node:fs"));
|
|
5197
5280
|
var path8 = __toESM(require("node:path"));
|
|
5281
|
+
function holdsLibrary(dir) {
|
|
5282
|
+
return fs9.existsSync(path8.join(dir, "library.json")) || fs9.existsSync(path8.join(dir, "library"));
|
|
5283
|
+
}
|
|
5284
|
+
function moveTarget(picked) {
|
|
5285
|
+
if (!fs9.existsSync(picked)) return picked;
|
|
5286
|
+
return fs9.readdirSync(picked).length ? path8.join(picked, "EDGEdeck") : picked;
|
|
5287
|
+
}
|
|
5288
|
+
function volume(p) {
|
|
5289
|
+
let dir = path8.resolve(p);
|
|
5290
|
+
for (; ; ) {
|
|
5291
|
+
try {
|
|
5292
|
+
return fs9.statSync(dir).dev;
|
|
5293
|
+
} catch {
|
|
5294
|
+
const up = path8.dirname(dir);
|
|
5295
|
+
if (up === dir) return void 0;
|
|
5296
|
+
dir = up;
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
function sameDrive(a, b) {
|
|
5301
|
+
const va = volume(a);
|
|
5302
|
+
const vb = volume(b);
|
|
5303
|
+
return va !== void 0 && va === vb;
|
|
5304
|
+
}
|
|
5305
|
+
function inside(child, parent) {
|
|
5306
|
+
const rel = path8.relative(parent, child);
|
|
5307
|
+
return !!rel && !rel.startsWith("..") && !path8.isAbsolute(rel);
|
|
5308
|
+
}
|
|
5309
|
+
function listFiles(root) {
|
|
5310
|
+
const out = [];
|
|
5311
|
+
const walk2 = (dir) => {
|
|
5312
|
+
for (const e of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
5313
|
+
const abs = path8.join(dir, e.name);
|
|
5314
|
+
if (e.isDirectory()) walk2(abs);
|
|
5315
|
+
else if (e.isFile()) out.push({ rel: path8.relative(root, abs), size: fs9.statSync(abs).size });
|
|
5316
|
+
}
|
|
5317
|
+
};
|
|
5318
|
+
walk2(root);
|
|
5319
|
+
return out;
|
|
5320
|
+
}
|
|
5321
|
+
async function moveLibrary(from, to, progress, cancelled) {
|
|
5322
|
+
const src = path8.resolve(from);
|
|
5323
|
+
const dest = path8.resolve(to);
|
|
5324
|
+
if (src.toLowerCase() === dest.toLowerCase()) throw new Error(`The library is already in ${dest}.`);
|
|
5325
|
+
if (inside(dest, src)) throw new Error(`${dest} is inside the library itself; pick a folder outside it.`);
|
|
5326
|
+
if (!holdsLibrary(src)) throw new Error(`${src} does not hold a library.`);
|
|
5327
|
+
const existed = fs9.existsSync(dest);
|
|
5328
|
+
if (existed && fs9.readdirSync(dest).length) throw new Error(`${dest} is not empty.`);
|
|
5329
|
+
if (sameDrive(src, dest)) {
|
|
5330
|
+
const files2 = listFiles(src);
|
|
5331
|
+
fs9.mkdirSync(path8.dirname(dest), { recursive: true });
|
|
5332
|
+
if (existed) fs9.rmdirSync(dest);
|
|
5333
|
+
fs9.renameSync(src, dest);
|
|
5334
|
+
return { how: "rename", files: files2.length, bytes: files2.reduce((n, f) => n + f.size, 0), leftBehind: [] };
|
|
5335
|
+
}
|
|
5336
|
+
const files = listFiles(src);
|
|
5337
|
+
const totalBytes = files.reduce((n, f) => n + f.size, 0);
|
|
5338
|
+
const done = { files: 0, totalFiles: files.length, bytes: 0, totalBytes };
|
|
5339
|
+
try {
|
|
5340
|
+
fs9.mkdirSync(dest, { recursive: true });
|
|
5341
|
+
for (const f of files) {
|
|
5342
|
+
if (cancelled?.()) throw new Error("Cancelled: the library stays where it was.");
|
|
5343
|
+
const a = path8.join(src, f.rel);
|
|
5344
|
+
const b = path8.join(dest, f.rel);
|
|
5345
|
+
fs9.mkdirSync(path8.dirname(b), { recursive: true });
|
|
5346
|
+
await fs9.promises.copyFile(a, b);
|
|
5347
|
+
const st = fs9.statSync(a);
|
|
5348
|
+
fs9.utimesSync(b, st.atime, st.mtime);
|
|
5349
|
+
if (fs9.statSync(b).size !== f.size) throw new Error(`${f.rel} did not copy whole.`);
|
|
5350
|
+
done.files++;
|
|
5351
|
+
done.bytes += f.size;
|
|
5352
|
+
progress?.({ ...done });
|
|
5353
|
+
}
|
|
5354
|
+
} catch (err) {
|
|
5355
|
+
fs9.rmSync(dest, { recursive: true, force: true });
|
|
5356
|
+
if (existed) fs9.mkdirSync(dest, { recursive: true });
|
|
5357
|
+
throw err;
|
|
5358
|
+
}
|
|
5359
|
+
const leftBehind = [];
|
|
5360
|
+
for (const f of files) {
|
|
5361
|
+
try {
|
|
5362
|
+
fs9.rmSync(path8.join(src, f.rel), { force: true });
|
|
5363
|
+
} catch {
|
|
5364
|
+
leftBehind.push(path8.join(src, f.rel));
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
try {
|
|
5368
|
+
fs9.rmSync(src, { recursive: true, force: true });
|
|
5369
|
+
} catch {
|
|
5370
|
+
}
|
|
5371
|
+
return { how: "copy", files: files.length, bytes: totalBytes, leftBehind };
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
// src/loadouts.ts
|
|
5375
|
+
var fs10 = __toESM(require("node:fs"));
|
|
5376
|
+
var path9 = __toESM(require("node:path"));
|
|
5198
5377
|
var DefaultLoadout = "Default";
|
|
5199
5378
|
function loadoutsFolder(root) {
|
|
5200
|
-
return
|
|
5379
|
+
return path9.join(root, "loadouts");
|
|
5201
5380
|
}
|
|
5202
5381
|
function slug(name) {
|
|
5203
5382
|
return name.replace(/[<>:"/\\|?*]+/g, "-").trim() || "loadout";
|
|
5204
5383
|
}
|
|
5205
5384
|
function readLoadout(file) {
|
|
5206
|
-
return JSON.parse(
|
|
5385
|
+
return JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
5207
5386
|
}
|
|
5208
5387
|
function listLoadouts(root) {
|
|
5209
5388
|
const dir = loadoutsFolder(root);
|
|
5210
|
-
if (!
|
|
5389
|
+
if (!fs10.existsSync(dir)) return [];
|
|
5211
5390
|
const out = [];
|
|
5212
|
-
for (const f of
|
|
5391
|
+
for (const f of fs10.readdirSync(dir)) {
|
|
5213
5392
|
if (!f.toLowerCase().endsWith(".json")) continue;
|
|
5214
5393
|
try {
|
|
5215
|
-
const l = readLoadout(
|
|
5216
|
-
out.push({ name: l.name, file:
|
|
5394
|
+
const l = readLoadout(path9.join(dir, f));
|
|
5395
|
+
out.push({ name: l.name, file: path9.join(dir, f), saved: l.saved, mods: l.mods.length, on: l.mods.filter((m) => m.enabled).length });
|
|
5217
5396
|
} catch {
|
|
5218
5397
|
}
|
|
5219
5398
|
}
|
|
@@ -5242,13 +5421,13 @@ function entryFor(f, mod, enabled) {
|
|
|
5242
5421
|
return { family: f.key, id: mod.id, name: f.name, version: mod.version, enabled, nexusModId: f.nexusId };
|
|
5243
5422
|
}
|
|
5244
5423
|
function writeLoadout(root, loadout, file) {
|
|
5245
|
-
|
|
5424
|
+
fs10.mkdirSync(loadoutsFolder(root), { recursive: true });
|
|
5246
5425
|
let target = file;
|
|
5247
5426
|
if (!target) {
|
|
5248
|
-
target =
|
|
5249
|
-
for (let n = 2;
|
|
5427
|
+
target = path9.join(loadoutsFolder(root), `${slug(loadout.name)}.json`);
|
|
5428
|
+
for (let n = 2; fs10.existsSync(target); n++) target = path9.join(loadoutsFolder(root), `${slug(loadout.name)}-${n}.json`);
|
|
5250
5429
|
}
|
|
5251
|
-
|
|
5430
|
+
fs10.writeFileSync(target, JSON.stringify(loadout, null, 2) + "\n");
|
|
5252
5431
|
return target;
|
|
5253
5432
|
}
|
|
5254
5433
|
function saveLoadout(lib, name, gameVersion2) {
|
|
@@ -5352,7 +5531,7 @@ function renameLoadout(lib, from, to) {
|
|
|
5352
5531
|
if (!info) throw new Error(`There is no loadout called ${from}.`);
|
|
5353
5532
|
const loadout = readLoadout(info.file);
|
|
5354
5533
|
loadout.name = to.trim();
|
|
5355
|
-
|
|
5534
|
+
fs10.rmSync(info.file);
|
|
5356
5535
|
writeLoadout(lib.root, loadout);
|
|
5357
5536
|
if (lib.info.activeLoadout === from) {
|
|
5358
5537
|
lib.info.activeLoadout = to.trim();
|
|
@@ -5362,18 +5541,18 @@ function renameLoadout(lib, from, to) {
|
|
|
5362
5541
|
function deleteLoadout(lib, name) {
|
|
5363
5542
|
if (lib.info.activeLoadout === name) throw new Error(`${name} is the active loadout; make another one active first.`);
|
|
5364
5543
|
const info = findLoadout(lib.root, name);
|
|
5365
|
-
if (info)
|
|
5544
|
+
if (info) fs10.rmSync(info.file);
|
|
5366
5545
|
}
|
|
5367
5546
|
function exportLoadout(lib, name, dest, gameVersion2) {
|
|
5368
5547
|
if (lib.info.activeLoadout === name) syncActiveLoadout(lib, gameVersion2);
|
|
5369
5548
|
const info = findLoadout(lib.root, name);
|
|
5370
5549
|
if (!info) throw new Error(`There is no loadout called ${name}.`);
|
|
5371
|
-
|
|
5550
|
+
fs10.copyFileSync(info.file, dest);
|
|
5372
5551
|
}
|
|
5373
5552
|
function importLoadout(lib, file) {
|
|
5374
5553
|
const loadout = readLoadout(file);
|
|
5375
|
-
if (!Array.isArray(loadout.mods)) throw new Error(`${
|
|
5376
|
-
let name = (loadout.name ||
|
|
5554
|
+
if (!Array.isArray(loadout.mods)) throw new Error(`${path9.basename(file)} is not a EDGEdeck loadout.`);
|
|
5555
|
+
let name = (loadout.name || path9.basename(file, ".json")).trim();
|
|
5377
5556
|
for (let n = 2; findLoadout(lib.root, name); n++) name = `${(loadout.name || "Loadout").trim()} (${n})`;
|
|
5378
5557
|
writeLoadout(lib.root, { ...loadout, name, saved: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5379
5558
|
return name;
|
|
@@ -5398,7 +5577,7 @@ function signIn(options) {
|
|
|
5398
5577
|
let done = false;
|
|
5399
5578
|
let attempts = 0;
|
|
5400
5579
|
let socket;
|
|
5401
|
-
return new Promise((
|
|
5580
|
+
return new Promise((resolve5, reject) => {
|
|
5402
5581
|
const finish = (err, key2) => {
|
|
5403
5582
|
if (done) return;
|
|
5404
5583
|
done = true;
|
|
@@ -5408,7 +5587,7 @@ function signIn(options) {
|
|
|
5408
5587
|
socket?.close();
|
|
5409
5588
|
} catch {
|
|
5410
5589
|
}
|
|
5411
|
-
if (key2)
|
|
5590
|
+
if (key2) resolve5(key2);
|
|
5412
5591
|
else reject(err ?? new Error("Nexus sign-in failed"));
|
|
5413
5592
|
};
|
|
5414
5593
|
const onAbort = () => finish(new Error("Sign-in cancelled"));
|
|
@@ -5539,7 +5718,7 @@ function signInWithOAuth(options) {
|
|
|
5539
5718
|
const { verifier, challenge } = pkce();
|
|
5540
5719
|
const state = crypto3.randomUUID();
|
|
5541
5720
|
let done = false;
|
|
5542
|
-
return new Promise((
|
|
5721
|
+
return new Promise((resolve5, reject) => {
|
|
5543
5722
|
const server = http.createServer();
|
|
5544
5723
|
const finish = (err, tokens) => {
|
|
5545
5724
|
if (done) return;
|
|
@@ -5547,7 +5726,7 @@ function signInWithOAuth(options) {
|
|
|
5547
5726
|
clearTimeout(timer);
|
|
5548
5727
|
options.signal?.removeEventListener("abort", onAbort);
|
|
5549
5728
|
server.close();
|
|
5550
|
-
if (tokens)
|
|
5729
|
+
if (tokens) resolve5(tokens);
|
|
5551
5730
|
else reject(err ?? new Error("Nexus sign-in failed"));
|
|
5552
5731
|
};
|
|
5553
5732
|
const onAbort = () => finish(new Error("Sign-in cancelled"));
|
|
@@ -5668,10 +5847,10 @@ function describeHandler(command) {
|
|
|
5668
5847
|
}
|
|
5669
5848
|
|
|
5670
5849
|
// src/project.ts
|
|
5671
|
-
var
|
|
5672
|
-
var
|
|
5850
|
+
var fs11 = __toESM(require("node:fs"));
|
|
5851
|
+
var path10 = __toESM(require("node:path"));
|
|
5673
5852
|
function addToProject(lib, mod, files, projectFile, replace = /* @__PURE__ */ new Set()) {
|
|
5674
|
-
const resources =
|
|
5853
|
+
const resources = path10.join(path10.dirname(projectFile), "source", "resources");
|
|
5675
5854
|
const out = { copied: [], same: [], kept: [], archives: [] };
|
|
5676
5855
|
for (const f of files) {
|
|
5677
5856
|
if (f.toLowerCase().endsWith(".archive")) {
|
|
@@ -5679,10 +5858,10 @@ function addToProject(lib, mod, files, projectFile, replace = /* @__PURE__ */ ne
|
|
|
5679
5858
|
continue;
|
|
5680
5859
|
}
|
|
5681
5860
|
const src = sourcePath(lib, mod, f);
|
|
5682
|
-
const rel =
|
|
5683
|
-
const dest =
|
|
5684
|
-
if (
|
|
5685
|
-
if (
|
|
5861
|
+
const rel = path10.join("source", "resources", ...f.split("/"));
|
|
5862
|
+
const dest = path10.join(resources, ...f.split("/"));
|
|
5863
|
+
if (fs11.existsSync(dest)) {
|
|
5864
|
+
if (fs11.readFileSync(dest).equals(fs11.readFileSync(src))) {
|
|
5686
5865
|
out.same.push(rel);
|
|
5687
5866
|
continue;
|
|
5688
5867
|
}
|
|
@@ -5691,39 +5870,39 @@ function addToProject(lib, mod, files, projectFile, replace = /* @__PURE__ */ ne
|
|
|
5691
5870
|
continue;
|
|
5692
5871
|
}
|
|
5693
5872
|
}
|
|
5694
|
-
|
|
5695
|
-
|
|
5873
|
+
fs11.mkdirSync(path10.dirname(dest), { recursive: true });
|
|
5874
|
+
fs11.copyFileSync(src, dest);
|
|
5696
5875
|
out.copied.push(rel);
|
|
5697
5876
|
}
|
|
5698
5877
|
return out;
|
|
5699
5878
|
}
|
|
5700
5879
|
function findProjectFile(target) {
|
|
5701
|
-
if (
|
|
5702
|
-
const files =
|
|
5703
|
-
return files.length ?
|
|
5880
|
+
if (fs11.existsSync(target) && fs11.statSync(target).isFile()) return target.toLowerCase().endsWith(".cpmodproj") ? target : void 0;
|
|
5881
|
+
const files = fs11.existsSync(target) ? fs11.readdirSync(target).filter((f) => f.toLowerCase().endsWith(".cpmodproj")) : [];
|
|
5882
|
+
return files.length ? path10.join(target, files[0]) : void 0;
|
|
5704
5883
|
}
|
|
5705
5884
|
function projectName(projectFile) {
|
|
5706
5885
|
try {
|
|
5707
|
-
const xml =
|
|
5886
|
+
const xml = fs11.readFileSync(projectFile, "utf8");
|
|
5708
5887
|
const m = /<ModName>\s*([^<]+?)\s*<\//.exec(xml) ?? /<Name>\s*([^<]+?)\s*<\//.exec(xml);
|
|
5709
5888
|
if (m) return m[1];
|
|
5710
5889
|
} catch {
|
|
5711
5890
|
}
|
|
5712
|
-
return
|
|
5891
|
+
return path10.basename(projectFile, path10.extname(projectFile));
|
|
5713
5892
|
}
|
|
5714
5893
|
function packedFolder(projectFile) {
|
|
5715
|
-
return
|
|
5894
|
+
return path10.join(path10.dirname(projectFile), "packed");
|
|
5716
5895
|
}
|
|
5717
5896
|
function classifyProject(projectFile) {
|
|
5718
5897
|
const packed = packedFolder(projectFile);
|
|
5719
|
-
if (!
|
|
5898
|
+
if (!fs11.existsSync(packed)) return { placements: [], kinds: [], unplaced: [], ignored: [] };
|
|
5720
5899
|
return classify(packed, projectName(projectFile));
|
|
5721
5900
|
}
|
|
5722
5901
|
function newest(dir) {
|
|
5723
5902
|
let t = 0;
|
|
5724
|
-
for (const e of
|
|
5725
|
-
const p =
|
|
5726
|
-
t = Math.max(t, e.isDirectory() ? newest(p) :
|
|
5903
|
+
for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
5904
|
+
const p = path10.join(dir, e.name);
|
|
5905
|
+
t = Math.max(t, e.isDirectory() ? newest(p) : fs11.statSync(p).mtimeMs);
|
|
5727
5906
|
}
|
|
5728
5907
|
return t;
|
|
5729
5908
|
}
|
|
@@ -5732,10 +5911,10 @@ function syncProjects(lib) {
|
|
|
5732
5911
|
for (const mod of lib.mods) {
|
|
5733
5912
|
if (mod.source.type !== "project" || !mod.source.path) continue;
|
|
5734
5913
|
const packed = packedFolder(mod.source.path);
|
|
5735
|
-
if (!
|
|
5914
|
+
if (!fs11.existsSync(packed)) continue;
|
|
5736
5915
|
const libNewest = mod.files.reduce((t, f) => {
|
|
5737
5916
|
try {
|
|
5738
|
-
return Math.max(t,
|
|
5917
|
+
return Math.max(t, fs11.statSync(sourcePath(lib, mod, f)).mtimeMs);
|
|
5739
5918
|
} catch {
|
|
5740
5919
|
return t;
|
|
5741
5920
|
}
|
|
@@ -5746,11 +5925,11 @@ function syncProjects(lib) {
|
|
|
5746
5925
|
const keep = /* @__PURE__ */ new Set();
|
|
5747
5926
|
for (const p of c.placements) {
|
|
5748
5927
|
const dest = sourcePath(lib, mod, p.to);
|
|
5749
|
-
|
|
5750
|
-
|
|
5928
|
+
fs11.mkdirSync(path10.dirname(dest), { recursive: true });
|
|
5929
|
+
fs11.copyFileSync(p.from, dest);
|
|
5751
5930
|
keep.add(p.to);
|
|
5752
5931
|
}
|
|
5753
|
-
for (const f of mod.files) if (!keep.has(f))
|
|
5932
|
+
for (const f of mod.files) if (!keep.has(f)) fs11.rmSync(sourcePath(lib, mod, f), { force: true });
|
|
5754
5933
|
mod.files = [...keep].sort();
|
|
5755
5934
|
mod.kinds = c.kinds;
|
|
5756
5935
|
saveMod(lib, mod);
|
|
@@ -5806,11 +5985,11 @@ function installProject(lib, projectFile, beside) {
|
|
|
5806
5985
|
const keep = /* @__PURE__ */ new Set();
|
|
5807
5986
|
for (const p of c.placements) {
|
|
5808
5987
|
const dest = sourcePath(lib, existing, p.to);
|
|
5809
|
-
|
|
5810
|
-
|
|
5988
|
+
fs11.mkdirSync(path10.dirname(dest), { recursive: true });
|
|
5989
|
+
fs11.copyFileSync(p.from, dest);
|
|
5811
5990
|
keep.add(p.to);
|
|
5812
5991
|
}
|
|
5813
|
-
for (const f of existing.files) if (!keep.has(f))
|
|
5992
|
+
for (const f of existing.files) if (!keep.has(f)) fs11.rmSync(sourcePath(lib, existing, f), { force: true });
|
|
5814
5993
|
existing.files = [...keep].sort();
|
|
5815
5994
|
existing.kinds = c.kinds;
|
|
5816
5995
|
activateVersion(lib, existing);
|
|
@@ -5821,12 +6000,12 @@ function installProject(lib, projectFile, beside) {
|
|
|
5821
6000
|
|
|
5822
6001
|
// src/redmod.ts
|
|
5823
6002
|
var import_node_child_process6 = require("node:child_process");
|
|
5824
|
-
var
|
|
5825
|
-
var
|
|
6003
|
+
var fs12 = __toESM(require("node:fs"));
|
|
6004
|
+
var path11 = __toESM(require("node:path"));
|
|
5826
6005
|
var import_node_util4 = require("node:util");
|
|
5827
6006
|
var run4 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
|
|
5828
6007
|
function redModExe(gamePath) {
|
|
5829
|
-
return
|
|
6008
|
+
return path11.join(gamePath, "tools", "redmod", "bin", "redMod.exe");
|
|
5830
6009
|
}
|
|
5831
6010
|
function enabledRedmodPackages(lib) {
|
|
5832
6011
|
const names = [];
|
|
@@ -5853,13 +6032,13 @@ function deployedRedmodPackages(lib, gamePath) {
|
|
|
5853
6032
|
async function deployRedmod(lib, gamePath, log, packages = enabledRedmodPackages(lib)) {
|
|
5854
6033
|
const exe = redModExe(gamePath);
|
|
5855
6034
|
if (!packages.length) return { ran: false, packages, output: "no REDmod packages enabled" };
|
|
5856
|
-
if (!
|
|
5857
|
-
const listFile =
|
|
5858
|
-
|
|
5859
|
-
const args = ["deploy", `-root=${gamePath}`, `-modlist=${listFile}`, `-rttiSchemaPath=${
|
|
6035
|
+
if (!fs12.existsSync(exe)) return { ran: false, packages, output: `redMod.exe not found at ${exe}; the REDmod tools are a free DLC on the game's store page` };
|
|
6036
|
+
const listFile = path11.join(lib.root, "redmod-modlist.txt");
|
|
6037
|
+
fs12.writeFileSync(listFile, packages.join("\r\n") + "\r\n");
|
|
6038
|
+
const args = ["deploy", `-root=${gamePath}`, `-modlist=${listFile}`, `-rttiSchemaPath=${path11.join(gamePath, "tools", "redmod", "metadata.json")}`];
|
|
5860
6039
|
log?.(`redMod.exe ${args.join(" ")}`);
|
|
5861
6040
|
try {
|
|
5862
|
-
const { stdout, stderr } = await run4(exe, args, { cwd:
|
|
6041
|
+
const { stdout, stderr } = await run4(exe, args, { cwd: path11.dirname(exe), windowsHide: true, maxBuffer: 1 << 26 });
|
|
5863
6042
|
const output = (stdout + stderr).trim();
|
|
5864
6043
|
for (const line of output.split(/\r?\n/)) if (line.trim()) log?.(` ${line}`);
|
|
5865
6044
|
return { ran: true, packages, output };
|
|
@@ -5977,8 +6156,8 @@ function setSectionOn(lib, name, on) {
|
|
|
5977
6156
|
}
|
|
5978
6157
|
|
|
5979
6158
|
// src/sizes.ts
|
|
5980
|
-
var
|
|
5981
|
-
var
|
|
6159
|
+
var fs13 = __toESM(require("node:fs"));
|
|
6160
|
+
var path12 = __toESM(require("node:path"));
|
|
5982
6161
|
var cache = /* @__PURE__ */ new Map();
|
|
5983
6162
|
function modSize(lib, mod) {
|
|
5984
6163
|
const stamp = `${mod.files.length}:${mod.added}:${lib.root}`;
|
|
@@ -5991,7 +6170,7 @@ function modSize(lib, mod) {
|
|
|
5991
6170
|
for (const f of mod.files) {
|
|
5992
6171
|
let n = 0;
|
|
5993
6172
|
try {
|
|
5994
|
-
const st =
|
|
6173
|
+
const st = fs13.statSync(path12.join(folder, ...f.split("/")));
|
|
5995
6174
|
n = st.size;
|
|
5996
6175
|
mtimes.set(f, st.mtimeMs);
|
|
5997
6176
|
} catch {
|
|
@@ -6010,10 +6189,10 @@ function forgetSizes(modId) {
|
|
|
6010
6189
|
function folderSize(dir) {
|
|
6011
6190
|
let total = 0;
|
|
6012
6191
|
try {
|
|
6013
|
-
for (const e of
|
|
6014
|
-
const p =
|
|
6192
|
+
for (const e of fs13.readdirSync(dir, { withFileTypes: true })) {
|
|
6193
|
+
const p = path12.join(dir, e.name);
|
|
6015
6194
|
if (e.isDirectory()) total += folderSize(p);
|
|
6016
|
-
else if (e.isFile()) total +=
|
|
6195
|
+
else if (e.isFile()) total += fs13.statSync(p).size;
|
|
6017
6196
|
}
|
|
6018
6197
|
} catch {
|
|
6019
6198
|
}
|
|
@@ -6034,20 +6213,20 @@ function formatBytes(n) {
|
|
|
6034
6213
|
}
|
|
6035
6214
|
|
|
6036
6215
|
// src/verbs.ts
|
|
6037
|
-
var
|
|
6216
|
+
var fs15 = __toESM(require("node:fs"));
|
|
6038
6217
|
var os3 = __toESM(require("node:os"));
|
|
6039
|
-
var
|
|
6218
|
+
var path14 = __toESM(require("node:path"));
|
|
6040
6219
|
|
|
6041
6220
|
// src/vortex.ts
|
|
6042
|
-
var
|
|
6043
|
-
var
|
|
6221
|
+
var fs14 = __toESM(require("node:fs"));
|
|
6222
|
+
var path13 = __toESM(require("node:path"));
|
|
6044
6223
|
function vortexStagingPath() {
|
|
6045
|
-
return
|
|
6224
|
+
return path13.join(process.env.APPDATA ?? path13.join(process.env.USERPROFILE ?? "", "AppData", "Roaming"), "Vortex", "cyberpunk2077", "mods");
|
|
6046
6225
|
}
|
|
6047
6226
|
function manifestStaging(gamePath) {
|
|
6048
6227
|
if (!gamePath) return void 0;
|
|
6049
6228
|
try {
|
|
6050
|
-
const m = JSON.parse(
|
|
6229
|
+
const m = JSON.parse(fs14.readFileSync(path13.join(gamePath, "vortex.deployment.json"), "utf8"));
|
|
6051
6230
|
return m.stagingPath || void 0;
|
|
6052
6231
|
} catch {
|
|
6053
6232
|
return void 0;
|
|
@@ -6055,18 +6234,18 @@ function manifestStaging(gamePath) {
|
|
|
6055
6234
|
}
|
|
6056
6235
|
function driveRoots(gamePath) {
|
|
6057
6236
|
if (process.platform !== "win32") return [];
|
|
6058
|
-
const first = gamePath ?
|
|
6237
|
+
const first = gamePath ? path13.parse(path13.resolve(gamePath)).root.toUpperCase() : "";
|
|
6059
6238
|
const roots = first ? [first] : [];
|
|
6060
6239
|
for (let c = 67; c <= 90; c++) {
|
|
6061
6240
|
const r = `${String.fromCharCode(c)}:\\`;
|
|
6062
|
-
if (r !== first &&
|
|
6241
|
+
if (r !== first && fs14.existsSync(r)) roots.push(r);
|
|
6063
6242
|
}
|
|
6064
6243
|
return roots;
|
|
6065
6244
|
}
|
|
6066
6245
|
function vortexStagingCandidates(gamePath, setting) {
|
|
6067
6246
|
const list = [setting, manifestStaging(gamePath), vortexStagingPath()];
|
|
6068
6247
|
for (const root of driveRoots(gamePath)) {
|
|
6069
|
-
list.push(
|
|
6248
|
+
list.push(path13.join(root, "Vortex Mods", "cyberpunk2077"), path13.join(root, "VortexMods", "cyberpunk2077"), path13.join(root, "Vortex", "cyberpunk2077", "mods"), path13.join(root, "Vortex", "mods", "cyberpunk2077"));
|
|
6070
6249
|
}
|
|
6071
6250
|
const seen = /* @__PURE__ */ new Set();
|
|
6072
6251
|
return list.filter((p) => !!p && !seen.has(p.toLowerCase()) && !!seen.add(p.toLowerCase()));
|
|
@@ -6074,7 +6253,7 @@ function vortexStagingCandidates(gamePath, setting) {
|
|
|
6074
6253
|
function findVortexStaging(gamePath, setting) {
|
|
6075
6254
|
for (const dir of vortexStagingCandidates(gamePath, setting)) {
|
|
6076
6255
|
try {
|
|
6077
|
-
if (
|
|
6256
|
+
if (fs14.readdirSync(dir, { withFileTypes: true }).some((e) => e.isDirectory() && !e.name.startsWith("__"))) return dir;
|
|
6078
6257
|
} catch {
|
|
6079
6258
|
}
|
|
6080
6259
|
}
|
|
@@ -6082,24 +6261,24 @@ function findVortexStaging(gamePath, setting) {
|
|
|
6082
6261
|
}
|
|
6083
6262
|
function countFiles(dir) {
|
|
6084
6263
|
let n = 0;
|
|
6085
|
-
for (const e of
|
|
6086
|
-
if (e.isDirectory()) n += countFiles(
|
|
6264
|
+
for (const e of fs14.readdirSync(dir, { withFileTypes: true })) {
|
|
6265
|
+
if (e.isDirectory()) n += countFiles(path13.join(dir, e.name));
|
|
6087
6266
|
else n++;
|
|
6088
6267
|
}
|
|
6089
6268
|
return n;
|
|
6090
6269
|
}
|
|
6091
6270
|
function guessName(folder) {
|
|
6092
6271
|
const sub = (...parts) => {
|
|
6093
|
-
const d =
|
|
6094
|
-
if (!
|
|
6095
|
-
const names =
|
|
6272
|
+
const d = path13.join(folder, ...parts);
|
|
6273
|
+
if (!fs14.existsSync(d)) return void 0;
|
|
6274
|
+
const names = fs14.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
6096
6275
|
return names.length === 1 ? names[0] : void 0;
|
|
6097
6276
|
};
|
|
6098
6277
|
const named = sub("r6", "scripts") ?? sub("bin", "x64", "plugins", "cyber_engine_tweaks", "mods") ?? sub("red4ext", "plugins") ?? sub("r6", "tweaks") ?? sub("mods");
|
|
6099
6278
|
if (named) return named;
|
|
6100
|
-
const archives =
|
|
6101
|
-
if (
|
|
6102
|
-
const a =
|
|
6279
|
+
const archives = path13.join(folder, "archive", "pc", "mod");
|
|
6280
|
+
if (fs14.existsSync(archives)) {
|
|
6281
|
+
const a = fs14.readdirSync(archives).find((f) => f.toLowerCase().endsWith(".archive"));
|
|
6103
6282
|
if (a) return a.replace(/\.archive$/i, "").replace(/^[#!_0-9]+/, "") || a;
|
|
6104
6283
|
}
|
|
6105
6284
|
return void 0;
|
|
@@ -6109,21 +6288,21 @@ function listVortexMods(gamePath, staging) {
|
|
|
6109
6288
|
if (!dir) return [];
|
|
6110
6289
|
staging = dir;
|
|
6111
6290
|
const deployed = /* @__PURE__ */ new Set();
|
|
6112
|
-
const manifest =
|
|
6113
|
-
if (
|
|
6291
|
+
const manifest = path13.join(gamePath, "vortex.deployment.json");
|
|
6292
|
+
if (fs14.existsSync(manifest)) {
|
|
6114
6293
|
try {
|
|
6115
|
-
const m = JSON.parse(
|
|
6294
|
+
const m = JSON.parse(fs14.readFileSync(manifest, "utf8"));
|
|
6116
6295
|
for (const f of m.files ?? []) deployed.add(f.source);
|
|
6117
6296
|
} catch {
|
|
6118
6297
|
}
|
|
6119
6298
|
}
|
|
6120
6299
|
const mods = [];
|
|
6121
|
-
for (const entry of
|
|
6300
|
+
for (const entry of fs14.readdirSync(staging, { withFileTypes: true })) {
|
|
6122
6301
|
if (!entry.isDirectory() || entry.name.startsWith("__")) continue;
|
|
6123
|
-
const folder =
|
|
6302
|
+
const folder = path13.join(staging, entry.name);
|
|
6124
6303
|
const files = countFiles(folder);
|
|
6125
6304
|
if (files === 0) continue;
|
|
6126
|
-
if (
|
|
6305
|
+
if (fs14.existsSync(path13.join(folder, "collection.json")) && files === 1) continue;
|
|
6127
6306
|
const m = /^(.*?)-(\d+)-([\d-]+)-(\d{10})$/.exec(entry.name);
|
|
6128
6307
|
const guid = /^[0-9a-f]{2}_[0-9a-f]{2}_[0-9a-f]{2}_[0-9a-f-]{36}$/i.test(entry.name);
|
|
6129
6308
|
mods.push({
|
|
@@ -6211,7 +6390,7 @@ var verbs = {
|
|
|
6211
6390
|
/** Every mod with its status and size, the library totals and the frameworks line. */
|
|
6212
6391
|
async list(s, largestFirst = false) {
|
|
6213
6392
|
const { lib, game } = s;
|
|
6214
|
-
const p =
|
|
6393
|
+
const p = fs15.existsSync(game) && isGameFolder(game) ? plan(lib, game) : void 0;
|
|
6215
6394
|
const manifest = p ? readManifest(game) : void 0;
|
|
6216
6395
|
const changes = p ? detectChanges(game) : [];
|
|
6217
6396
|
const rows = lib.mods.map((m) => ({ id: m.id, name: m.name, version: m.version, enabled: m.enabled, kinds: m.kinds, files: m.files.length, bytes: modSize(lib, m).total, status: p ? modStatus(lib, game, m, p, manifest, changes) : "" }));
|
|
@@ -6253,14 +6432,14 @@ ${mod.files.join("\n")}`;
|
|
|
6253
6432
|
},
|
|
6254
6433
|
/** Adds a zip, 7z, rar, a single mod file or a folder. An archive with options needs the option's label. */
|
|
6255
6434
|
async add(s, source, name, option) {
|
|
6256
|
-
if (!
|
|
6257
|
-
const isFile =
|
|
6258
|
-
const modName = name ??
|
|
6435
|
+
if (!fs15.existsSync(source)) throw new Error(`${source} does not exist`);
|
|
6436
|
+
const isFile = fs15.statSync(source).isFile();
|
|
6437
|
+
const modName = name ?? path14.basename(source, isFile ? path14.extname(source) : "");
|
|
6259
6438
|
let folder = source;
|
|
6260
6439
|
let temp;
|
|
6261
6440
|
try {
|
|
6262
6441
|
if (isFile) {
|
|
6263
|
-
temp =
|
|
6442
|
+
temp = fs15.mkdtempSync(path14.join(os3.tmpdir(), "edgedeck-verb-"));
|
|
6264
6443
|
await unpackOrCopy(source, temp, s.sevenZa);
|
|
6265
6444
|
folder = temp;
|
|
6266
6445
|
}
|
|
@@ -6277,7 +6456,7 @@ ${mod.files.join("\n")}`;
|
|
|
6277
6456
|
const note = settled.replaced ? `; a new version of ${settled.replaced.name} ${settled.replaced.version}, which is off now` : settled.older ? `; older than ${settled.older.version}, so it is off` : "";
|
|
6278
6457
|
return { data: { added: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, enabled: mod.enabled }, replaced: settled.replaced?.id, unplaced: c.unplaced }, text: `added ${mod.id} (${mod.files.length} files, ${mod.kinds.join(", ")})${note}; ${mod.enabled ? "it is on, " : ""}deploy to put it in the game${c.unplaced.length ? `; not placed: ${c.unplaced.join(", ")}` : ""}` };
|
|
6279
6458
|
} finally {
|
|
6280
|
-
if (temp)
|
|
6459
|
+
if (temp) removeTemp(temp);
|
|
6281
6460
|
}
|
|
6282
6461
|
},
|
|
6283
6462
|
/** Adds a REDcode or WolvenKit project's packed output, or refreshes the mod that follows it, and turns it on. */
|
|
@@ -6287,9 +6466,9 @@ ${mod.files.join("\n")}`;
|
|
|
6287
6466
|
* place). Without it, the project is a mod of its own and the text says what it sits beside.
|
|
6288
6467
|
*/
|
|
6289
6468
|
installProject(s, projectFile, sameName) {
|
|
6290
|
-
if (!
|
|
6469
|
+
if (!fs15.existsSync(projectFile)) throw new Error(`${projectFile} does not exist`);
|
|
6291
6470
|
const other = sameNameMod(s.lib, projectFile);
|
|
6292
|
-
const before = other && `${other.id}${other.source.type === "project" && other.source.path ? ` (${
|
|
6471
|
+
const before = other && `${other.id}${other.source.type === "project" && other.source.path ? ` (${path14.dirname(other.source.path)})` : ""}`;
|
|
6293
6472
|
const { mod, added, beside } = installProject(s.lib, projectFile, other && sameName ? { mod: other, how: sameName } : void 0);
|
|
6294
6473
|
const note = beside?.how === "follow" ? `; ${before} follows this project now` : beside ? `; another version of ${before}, on in its place` : other ? `; note: the library also has ${before} under the same name, a separate mod; the one higher in the list wins the files they share (call again with sameName follow or version to settle it)` : "";
|
|
6295
6474
|
return {
|
|
@@ -6373,7 +6552,7 @@ ${mod.files.join("\n")}`;
|
|
|
6373
6552
|
}
|
|
6374
6553
|
if (action === "load") {
|
|
6375
6554
|
if (!name) throw new Error("loadout load needs a name or file");
|
|
6376
|
-
const target =
|
|
6555
|
+
const target = fs15.existsSync(name) && name.toLowerCase().endsWith(".json") ? importLoadout(s.lib, name) : name;
|
|
6377
6556
|
const r = makeActive(s.lib, target);
|
|
6378
6557
|
return { data: { ...r, active: target }, text: `loadout ${target} active: ${r.on} on, ${r.off} off${r.missing.length ? `; not in the library: ${r.missing.join(", ")}` : ""}; deploy to apply` };
|
|
6379
6558
|
}
|
|
@@ -6397,6 +6576,7 @@ ${mod.files.join("\n")}`;
|
|
|
6397
6576
|
BridgeClient,
|
|
6398
6577
|
BridgeRepo,
|
|
6399
6578
|
DefaultLoadout,
|
|
6579
|
+
FrameworkSources,
|
|
6400
6580
|
FrameworksSection,
|
|
6401
6581
|
KnownModNames,
|
|
6402
6582
|
ManifestName,
|
|
@@ -6434,6 +6614,7 @@ ${mod.files.join("\n")}`;
|
|
|
6434
6614
|
deleteLoadout,
|
|
6435
6615
|
deleteSection,
|
|
6436
6616
|
deploy,
|
|
6617
|
+
deployMethod,
|
|
6437
6618
|
deployRedmod,
|
|
6438
6619
|
deployReportsFolder,
|
|
6439
6620
|
deployedRedmodPackages,
|
|
@@ -6465,12 +6646,15 @@ ${mod.files.join("\n")}`;
|
|
|
6465
6646
|
formatBytes,
|
|
6466
6647
|
forwarderCommand,
|
|
6467
6648
|
frameworkDetail,
|
|
6649
|
+
frameworkFamily,
|
|
6650
|
+
frameworkStates,
|
|
6468
6651
|
frameworkSummary,
|
|
6469
6652
|
gameExe,
|
|
6470
6653
|
gameRunning,
|
|
6471
6654
|
gameVersion,
|
|
6472
6655
|
guessName,
|
|
6473
6656
|
hasWebSocket,
|
|
6657
|
+
holdsLibrary,
|
|
6474
6658
|
idFor,
|
|
6475
6659
|
importFromVortex,
|
|
6476
6660
|
importLoadout,
|
|
@@ -6482,6 +6666,7 @@ ${mod.files.join("\n")}`;
|
|
|
6482
6666
|
isGameFolder,
|
|
6483
6667
|
keepDownload,
|
|
6484
6668
|
latestBridgeRelease,
|
|
6669
|
+
latestFrameworkRelease,
|
|
6485
6670
|
launchTarget,
|
|
6486
6671
|
libraryFolder,
|
|
6487
6672
|
librarySize,
|
|
@@ -6494,6 +6679,7 @@ ${mod.files.join("\n")}`;
|
|
|
6494
6679
|
lostFiles,
|
|
6495
6680
|
makeActive,
|
|
6496
6681
|
md5Of,
|
|
6682
|
+
missingFrameworks,
|
|
6497
6683
|
modDeployPaths,
|
|
6498
6684
|
modFolder,
|
|
6499
6685
|
modPageUrl,
|
|
@@ -6502,8 +6688,10 @@ ${mod.files.join("\n")}`;
|
|
|
6502
6688
|
modsRemoved,
|
|
6503
6689
|
modsTouched,
|
|
6504
6690
|
moveInSection,
|
|
6691
|
+
moveLibrary,
|
|
6505
6692
|
moveMod,
|
|
6506
6693
|
moveSection,
|
|
6694
|
+
moveTarget,
|
|
6507
6695
|
needsRefresh,
|
|
6508
6696
|
newLoadout,
|
|
6509
6697
|
newestFile,
|
|
@@ -6530,9 +6718,11 @@ ${mod.files.join("\n")}`;
|
|
|
6530
6718
|
registerNxmHandler,
|
|
6531
6719
|
removeMod,
|
|
6532
6720
|
removeOlderBridges,
|
|
6721
|
+
removeTemp,
|
|
6533
6722
|
renameFamily,
|
|
6534
6723
|
renameLoadout,
|
|
6535
6724
|
renameSection,
|
|
6725
|
+
sameDrive,
|
|
6536
6726
|
sameNameMod,
|
|
6537
6727
|
saveLibrary,
|
|
6538
6728
|
saveLoadout,
|
|
@@ -6545,6 +6735,7 @@ ${mod.files.join("\n")}`;
|
|
|
6545
6735
|
settleNewVersion,
|
|
6546
6736
|
signIn,
|
|
6547
6737
|
signInWithOAuth,
|
|
6738
|
+
singleRednodeApp,
|
|
6548
6739
|
snapshot,
|
|
6549
6740
|
sourcePath,
|
|
6550
6741
|
ssoPageUrl,
|
package/dist/mcp.js
CHANGED
|
@@ -2984,7 +2984,7 @@ var require_compile = __commonJS({
|
|
|
2984
2984
|
const schOrFunc = root.refs[ref];
|
|
2985
2985
|
if (schOrFunc)
|
|
2986
2986
|
return schOrFunc;
|
|
2987
|
-
let _sch =
|
|
2987
|
+
let _sch = resolve5.call(this, root, ref);
|
|
2988
2988
|
if (_sch === void 0) {
|
|
2989
2989
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2990
2990
|
const { schemaId } = this.opts;
|
|
@@ -3011,7 +3011,7 @@ var require_compile = __commonJS({
|
|
|
3011
3011
|
function sameSchemaEnv(s1, s2) {
|
|
3012
3012
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3013
3013
|
}
|
|
3014
|
-
function
|
|
3014
|
+
function resolve5(root, ref) {
|
|
3015
3015
|
let sch;
|
|
3016
3016
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3017
3017
|
ref = sch;
|
|
@@ -3841,7 +3841,7 @@ var require_fast_uri = __commonJS({
|
|
|
3841
3841
|
}
|
|
3842
3842
|
return uri;
|
|
3843
3843
|
}
|
|
3844
|
-
function
|
|
3844
|
+
function resolve5(baseURI, relativeURI, options2) {
|
|
3845
3845
|
const schemelessOptions = options2 ? Object.assign({ scheme: "null" }, options2) : { scheme: "null" };
|
|
3846
3846
|
const {
|
|
3847
3847
|
parsed: baseParsed,
|
|
@@ -4210,7 +4210,7 @@ var require_fast_uri = __commonJS({
|
|
|
4210
4210
|
var fastUri = {
|
|
4211
4211
|
SCHEMES,
|
|
4212
4212
|
normalize,
|
|
4213
|
-
resolve:
|
|
4213
|
+
resolve: resolve5,
|
|
4214
4214
|
resolveComponent,
|
|
4215
4215
|
equal,
|
|
4216
4216
|
serialize,
|
|
@@ -15901,7 +15901,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
15901
15901
|
var NONE = 0;
|
|
15902
15902
|
var ASSUMED = 1;
|
|
15903
15903
|
var PROVEN = 2;
|
|
15904
|
-
function isRecursive(inst, stack,
|
|
15904
|
+
function isRecursive(inst, stack, resolve5) {
|
|
15905
15905
|
const cached2 = recursive.get(inst);
|
|
15906
15906
|
if (cached2 !== void 0)
|
|
15907
15907
|
return cached2 ? PROVEN : NONE;
|
|
@@ -15911,7 +15911,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
15911
15911
|
let result = NONE;
|
|
15912
15912
|
const check2 = (child) => {
|
|
15913
15913
|
if (result !== PROVEN && child?._zod) {
|
|
15914
|
-
const answer = isRecursive(child, stack,
|
|
15914
|
+
const answer = isRecursive(child, stack, resolve5);
|
|
15915
15915
|
if (answer > result)
|
|
15916
15916
|
result = answer;
|
|
15917
15917
|
}
|
|
@@ -15922,7 +15922,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
15922
15922
|
const desc = Object.getOwnPropertyDescriptor(sh, key2);
|
|
15923
15923
|
if (spread && !desc.enumerable)
|
|
15924
15924
|
continue;
|
|
15925
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
15925
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
|
|
15926
15926
|
if (child > answer)
|
|
15927
15927
|
answer = child;
|
|
15928
15928
|
}
|
|
@@ -15986,7 +15986,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
15986
15986
|
break;
|
|
15987
15987
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
15988
15988
|
case "lazy": {
|
|
15989
|
-
const inner = def._cachedInner ?? (
|
|
15989
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
15990
15990
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
15991
15991
|
break;
|
|
15992
15992
|
}
|
|
@@ -34424,7 +34424,7 @@ var Protocol = class {
|
|
|
34424
34424
|
return;
|
|
34425
34425
|
}
|
|
34426
34426
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
34427
|
-
await new Promise((
|
|
34427
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
|
|
34428
34428
|
options2?.signal?.throwIfAborted();
|
|
34429
34429
|
}
|
|
34430
34430
|
} catch (error62) {
|
|
@@ -34441,7 +34441,7 @@ var Protocol = class {
|
|
|
34441
34441
|
*/
|
|
34442
34442
|
request(request, resultSchema, options2) {
|
|
34443
34443
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options2 ?? {};
|
|
34444
|
-
return new Promise((
|
|
34444
|
+
return new Promise((resolve5, reject) => {
|
|
34445
34445
|
const earlyReject = (error62) => {
|
|
34446
34446
|
reject(error62);
|
|
34447
34447
|
};
|
|
@@ -34519,7 +34519,7 @@ var Protocol = class {
|
|
|
34519
34519
|
if (!parseResult.success) {
|
|
34520
34520
|
reject(parseResult.error);
|
|
34521
34521
|
} else {
|
|
34522
|
-
|
|
34522
|
+
resolve5(parseResult.data);
|
|
34523
34523
|
}
|
|
34524
34524
|
} catch (error62) {
|
|
34525
34525
|
reject(error62);
|
|
@@ -34781,12 +34781,12 @@ var Protocol = class {
|
|
|
34781
34781
|
}
|
|
34782
34782
|
} catch {
|
|
34783
34783
|
}
|
|
34784
|
-
return new Promise((
|
|
34784
|
+
return new Promise((resolve5, reject) => {
|
|
34785
34785
|
if (signal.aborted) {
|
|
34786
34786
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
34787
34787
|
return;
|
|
34788
34788
|
}
|
|
34789
|
-
const timeoutId = setTimeout(
|
|
34789
|
+
const timeoutId = setTimeout(resolve5, interval);
|
|
34790
34790
|
signal.addEventListener("abort", () => {
|
|
34791
34791
|
clearTimeout(timeoutId);
|
|
34792
34792
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -35917,7 +35917,7 @@ var McpServer = class {
|
|
|
35917
35917
|
let task = createTaskResult.task;
|
|
35918
35918
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
35919
35919
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
35920
|
-
await new Promise((
|
|
35920
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
|
|
35921
35921
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
35922
35922
|
if (!updatedTask) {
|
|
35923
35923
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -36581,12 +36581,12 @@ var StdioServerTransport = class {
|
|
|
36581
36581
|
this.onclose?.();
|
|
36582
36582
|
}
|
|
36583
36583
|
send(message) {
|
|
36584
|
-
return new Promise((
|
|
36584
|
+
return new Promise((resolve5) => {
|
|
36585
36585
|
const json2 = serializeMessage(message);
|
|
36586
36586
|
if (this._stdout.write(json2)) {
|
|
36587
|
-
|
|
36587
|
+
resolve5();
|
|
36588
36588
|
} else {
|
|
36589
|
-
this._stdout.once("drain",
|
|
36589
|
+
this._stdout.once("drain", resolve5);
|
|
36590
36590
|
}
|
|
36591
36591
|
});
|
|
36592
36592
|
}
|
|
@@ -36665,7 +36665,8 @@ function folderName(name) {
|
|
|
36665
36665
|
return name.replace(/[<>:"/\\|?*]+/g, "").trim() || "Mod";
|
|
36666
36666
|
}
|
|
36667
36667
|
function rednodeManifest(placements) {
|
|
36668
|
-
const
|
|
36668
|
+
const to = singleRednodeApp(placements.map((x) => x.to));
|
|
36669
|
+
const p = to === void 0 ? void 0 : placements.find((x) => x.to === to);
|
|
36669
36670
|
if (!p) return void 0;
|
|
36670
36671
|
try {
|
|
36671
36672
|
const m = JSON.parse(fs.readFileSync(p.from, "utf8"));
|
|
@@ -36675,6 +36676,12 @@ function rednodeManifest(placements) {
|
|
|
36675
36676
|
return void 0;
|
|
36676
36677
|
}
|
|
36677
36678
|
}
|
|
36679
|
+
function singleRednodeApp(paths) {
|
|
36680
|
+
const manifests = paths.filter((f) => /^red4ext\/plugins\/REDnode\/apps\/[^/]+\/rednode-app\.json$/i.test(f));
|
|
36681
|
+
if (manifests.length !== 1) return void 0;
|
|
36682
|
+
if (paths.some((f) => /^red4ext\/plugins\/REDnode\//i.test(f) && !/^red4ext\/plugins\/REDnode\/apps\//i.test(f))) return void 0;
|
|
36683
|
+
return manifests[0];
|
|
36684
|
+
}
|
|
36678
36685
|
function rednodeId(manifest) {
|
|
36679
36686
|
try {
|
|
36680
36687
|
const m = JSON.parse(fs.readFileSync(manifest, "utf8"));
|
|
@@ -36925,11 +36932,8 @@ function baseName(name) {
|
|
|
36925
36932
|
return name.toLowerCase().replace(/[_\-]+/g, " ").replace(/\bv?\d+(?:\.\d+)+[a-z0-9]*\b/g, " ").replace(/[^a-z0-9]+/g, " ").trim();
|
|
36926
36933
|
}
|
|
36927
36934
|
function rednodePackageId(mod) {
|
|
36928
|
-
|
|
36929
|
-
|
|
36930
|
-
if (m) return m[1].toLowerCase();
|
|
36931
|
-
}
|
|
36932
|
-
return void 0;
|
|
36935
|
+
const manifest = singleRednodeApp(mod.files);
|
|
36936
|
+
return manifest ? /^red4ext\/plugins\/REDnode\/apps\/([^/]+)\//i.exec(manifest)[1].toLowerCase() : void 0;
|
|
36933
36937
|
}
|
|
36934
36938
|
function familyKey(mod) {
|
|
36935
36939
|
if (mod.family) return mod.family;
|
|
@@ -37148,6 +37152,21 @@ function canHardlink(libraryRoot, gamePath) {
|
|
|
37148
37152
|
return false;
|
|
37149
37153
|
}
|
|
37150
37154
|
}
|
|
37155
|
+
var hardlinkable = /* @__PURE__ */ new Map();
|
|
37156
|
+
function deployMethod(libraryRoot, gamePath, current) {
|
|
37157
|
+
try {
|
|
37158
|
+
if (fs4.statSync(libraryRoot).dev !== fs4.statSync(gamePath).dev) return "copy";
|
|
37159
|
+
} catch {
|
|
37160
|
+
return current ?? "copy";
|
|
37161
|
+
}
|
|
37162
|
+
if (current === "link") return "link";
|
|
37163
|
+
const k = `${path4.resolve(libraryRoot).toLowerCase()}|${path4.resolve(gamePath).toLowerCase()}`;
|
|
37164
|
+
if (!hardlinkable.has(k)) hardlinkable.set(k, canHardlink(libraryRoot, gamePath));
|
|
37165
|
+
return hardlinkable.get(k) ? "link" : "copy";
|
|
37166
|
+
}
|
|
37167
|
+
function otherMethod(entry, method) {
|
|
37168
|
+
return method === "link" !== !!entry.ino;
|
|
37169
|
+
}
|
|
37151
37170
|
function sourceReplaced(entry, method, d) {
|
|
37152
37171
|
if (d.content !== void 0) return false;
|
|
37153
37172
|
const src = identity(d.src);
|
|
@@ -37155,10 +37174,10 @@ function sourceReplaced(entry, method, d) {
|
|
|
37155
37174
|
if (method === "link" && entry.ino) return src.ino !== entry.ino;
|
|
37156
37175
|
return src.size !== entry.size || Math.abs(src.mtimeMs - entry.mtimeMs) >= 2e3;
|
|
37157
37176
|
}
|
|
37158
|
-
function stillOurs(entry,
|
|
37177
|
+
function stillOurs(entry, _method, gamePath) {
|
|
37159
37178
|
const id = identity(gameFile(gamePath, entry.path));
|
|
37160
37179
|
if (!id) return "missing";
|
|
37161
|
-
if (
|
|
37180
|
+
if (entry.ino) {
|
|
37162
37181
|
if (id.ino !== entry.ino) return "changed";
|
|
37163
37182
|
if (id.nlink < 2 && entry.mod !== Generated) return "sourceMissing";
|
|
37164
37183
|
return "ok";
|
|
@@ -37174,7 +37193,7 @@ function generatedDiffers(file2, content) {
|
|
|
37174
37193
|
}
|
|
37175
37194
|
function plan(lib, gamePath) {
|
|
37176
37195
|
const manifest = readManifest(gamePath);
|
|
37177
|
-
const method =
|
|
37196
|
+
const method = deployMethod(lib.root, gamePath, manifest?.method);
|
|
37178
37197
|
const desired = /* @__PURE__ */ new Map();
|
|
37179
37198
|
const conflicts = /* @__PURE__ */ new Map();
|
|
37180
37199
|
for (const mod of lib.mods) {
|
|
@@ -37205,6 +37224,7 @@ function plan(lib, gamePath) {
|
|
|
37205
37224
|
if (!e || (d.mod?.id ?? Generated) !== e.mod) return true;
|
|
37206
37225
|
if (d.content !== void 0 && generatedDiffers(gameFile(gamePath, d.rel), d.content)) return true;
|
|
37207
37226
|
if (stillOurs(e, method, gamePath) !== "ok") return true;
|
|
37227
|
+
if (d.content === void 0 && otherMethod(e, method)) return true;
|
|
37208
37228
|
return sourceReplaced(e, method, d);
|
|
37209
37229
|
});
|
|
37210
37230
|
return { method, desired: [...desired.values()], conflicts: [...conflicts.values()], stale, add, vortex: vortexManages(gamePath) };
|
|
@@ -37336,6 +37356,7 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
37336
37356
|
const p = plan(lib, gamePath);
|
|
37337
37357
|
const manifest = readManifest(gamePath) ?? { version: 1, library: lib.root, gamePath, method: p.method, time: "", files: [], dirs: [] };
|
|
37338
37358
|
manifest.library = lib.root;
|
|
37359
|
+
manifest.method = p.method;
|
|
37339
37360
|
const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0, lost: lostFiles(p) };
|
|
37340
37361
|
const entries = new Map(manifest.files.map((e) => [key(e.path), e]));
|
|
37341
37362
|
const scope = only ? modDeployPaths(gamePath, p, only) : void 0;
|
|
@@ -37462,6 +37483,7 @@ function pendingChanges(lib, gamePath, p) {
|
|
|
37462
37483
|
const st = stillOurs(e, p.method, gamePath);
|
|
37463
37484
|
if (st === "missing") out.push({ path: d.rel, mod: id, kind: "restore", detail: "missing from the game folder" });
|
|
37464
37485
|
else if (st === "changed") out.push({ path: d.rel, mod: id, kind: "restore", detail: "changed in the game folder; deploy asks whether to keep it" });
|
|
37486
|
+
else if (d.content === void 0 && otherMethod(e, p.method)) out.push({ path: d.rel, mod: id, kind: "relink", detail: p.method === "link" ? "the library is on the game's drive now: a hardlink instead of a copy" : "the library is on another drive now: a copy instead of a hardlink" });
|
|
37465
37487
|
else out.push({ path: d.rel, mod: id, kind: "relink", detail: d.content !== void 0 ? "the generated content changed (load order)" : "the library file was replaced (new version or rebuild)" });
|
|
37466
37488
|
}
|
|
37467
37489
|
}
|
|
@@ -37512,6 +37534,14 @@ function findRarTool() {
|
|
|
37512
37534
|
if (unrar) return { exe: unrar, args: (file2, dest) => ["x", "-y", "-o+", file2, dest + path5.sep] };
|
|
37513
37535
|
return void 0;
|
|
37514
37536
|
}
|
|
37537
|
+
function removeTemp(dir) {
|
|
37538
|
+
try {
|
|
37539
|
+
fs5.rmSync(dir, { recursive: true, force: true, maxRetries: 6, retryDelay: 250 });
|
|
37540
|
+
return void 0;
|
|
37541
|
+
} catch (err) {
|
|
37542
|
+
return `could not remove the temporary folder ${dir} (${err instanceof Error ? err.message : String(err)}); Windows clears its temp folder in time`;
|
|
37543
|
+
}
|
|
37544
|
+
}
|
|
37515
37545
|
async function unpackOrCopy(file2, dest, sevenZa) {
|
|
37516
37546
|
if (isArchiveFile(file2)) return extractArchive(file2, dest, sevenZa);
|
|
37517
37547
|
fs5.mkdirSync(dest, { recursive: true });
|
|
@@ -38112,7 +38142,7 @@ ${mod.files.join("\n")}`;
|
|
|
38112
38142
|
const note = settled.replaced ? `; a new version of ${settled.replaced.name} ${settled.replaced.version}, which is off now` : settled.older ? `; older than ${settled.older.version}, so it is off` : "";
|
|
38113
38143
|
return { data: { added: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, enabled: mod.enabled }, replaced: settled.replaced?.id, unplaced: c.unplaced }, text: `added ${mod.id} (${mod.files.length} files, ${mod.kinds.join(", ")})${note}; ${mod.enabled ? "it is on, " : ""}deploy to put it in the game${c.unplaced.length ? `; not placed: ${c.unplaced.join(", ")}` : ""}` };
|
|
38114
38144
|
} finally {
|
|
38115
|
-
if (temp)
|
|
38145
|
+
if (temp) removeTemp(temp);
|
|
38116
38146
|
}
|
|
38117
38147
|
},
|
|
38118
38148
|
/** Adds a REDcode or WolvenKit project's packed output, or refreshes the mod that follows it, and turns it on. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgemodders/edgedeck-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "EDGEdeck's MCP server and command line: manage Cyberpunk 2077 mods (add, turn on or off, order, deploy, undo, conflicts, loadouts) from an AI assistant or a script.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Edgemodders",
|