Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
80473f5
Switched to the new upload API
Aragas Apr 7, 2026
58b43a7
Merge branch 'master' into APP-237
Aragas Apr 7, 2026
8fb9af1
Merge branch 'master' into APP-237
Aragas Apr 13, 2026
4929414
Merge branch 'master' into APP-237
Aragas Apr 13, 2026
179fd80
Fixed tests
Aragas Apr 13, 2026
eec70c6
Merge branch 'master' into APP-237
Aragas Apr 21, 2026
b637825
Removed GraphQL, using new API
Aragas Apr 22, 2026
3c53140
Multipart is parallel now
Aragas Apr 22, 2026
579a40d
Fix
Aragas Apr 22, 2026
ed6c3f0
Better error handling, added abort handling
Aragas Apr 22, 2026
8cebdd9
Fixed bundle upload
Aragas Apr 22, 2026
8867ea7
No more parallel uploads
Aragas Apr 22, 2026
fd201fc
updating cherry-pick workflow to follow new schema
IDCs May 13, 2026
10941c8
another workflow that needs modifying
IDCs May 13, 2026
9141b74
Merge pull request #23147 from Nexus-Mods/chore/update-cherry-pick-wo…
IDCs May 13, 2026
4cf0c77
fixed notification suppress action icon showing a "cog/gear"
IDCs May 13, 2026
6e86c50
Installer sign fix
Aragas May 13, 2026
ea9e89e
Merge pull request #23153 from Nexus-Mods/task/sign-fix
Aragas May 13, 2026
93a0ff1
fix Tailwind source detection for renderer
IDCs May 13, 2026
f7b6c73
modifying package inputs too
IDCs May 13, 2026
ee4a997
Merge pull request #23158 from Nexus-Mods/fix/tailwind-styles
IDCs May 13, 2026
6f0c9f9
ensure that the main workflow runs for the new release/* branches
IDCs May 13, 2026
ae7f4a2
Merge pull request #23152 from Nexus-Mods/fix/app-454
IDCs May 13, 2026
1064253
Merge pull request #23160 from Nexus-Mods/chore/build-ci
IDCs May 13, 2026
c0d451c
toggle old tools dashlet based on UI layout
IDCs May 13, 2026
2849d67
added it for push, but somehow forgot to add it for PRs...
IDCs May 13, 2026
0706d51
Merge pull request #23166 from Nexus-Mods/fix/app-456
IDCs May 13, 2026
d1812fd
Merge pull request #23167 from Nexus-Mods/chore/build-workflow-on-pr
IDCs May 13, 2026
1348176
Merge branch 'master' into origin_release/v2.1
Aragas May 13, 2026
4ef0156
Merge branch 'v2.1-master-merge' into APP-237
Aragas May 18, 2026
858d58c
Replaced the graphql based collection edit
Aragas May 18, 2026
ffdd0ae
Merge branch 'master' into APP-237
Aragas May 18, 2026
0741307
Formatting
Aragas May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion etc/vortex.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import type { IModFileContentSearchFilter } from '@nexusmods/nexus-api';
import type { IModInfo } from 'modmeta-db';
import type { IModInfo as IModInfo_2 } from '@nexusmods/nexus-api';
import type { IModRequirements } from '@nexusmods/nexus-api';
import type { IncomingHttpHeaders } from 'http';
import type { IncomingMessage } from 'http';
import type { IPreference } from '@nexusmods/nexus-api';
import type { IPreferenceQuery } from '@nexusmods/nexus-api';
Expand Down Expand Up @@ -4067,6 +4068,16 @@ interface IUnavailableReason {
solution?: (t: TFunction) => string;
}

// @public (undocumented)
interface IUploadResult {
// (undocumented)
body: Buffer;
// (undocumented)
headers: IncomingHttpHeaders;
// (undocumented)
statusCode: number;
}

// @public
interface IUser {
// (undocumented)
Expand Down Expand Up @@ -6032,6 +6043,9 @@ type UpdateType = "drag-n-drop" | "props-update" | "refresh";
// @public (undocumented)
function upload(targetUrl: string, dataStream: Readable, dataSize: number): Promise<Buffer>;

// @public (undocumented)
function uploadWithHeaders(targetUrl: string, dataStream: Readable, dataSize: number, extraHeaders?: Record<string, string>): Promise<IUploadResult>;

// Warning: (ae-forgotten-export) The symbol "IUsageProps" needs to be exported by the entry point api.d.ts
//
// @public (undocumented)
Expand Down Expand Up @@ -6205,8 +6219,10 @@ declare namespace util {
jsonRequest,
request,
upload,
uploadWithHeaders,
IRequestOptions,
Method
Method,
IUploadResult
}
}
export { util }
Expand Down
1 change: 1 addition & 0 deletions extensions/collections/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"7z-bin": "catalog:",
"@hot-updater/bsdiff": "catalog:",
"@nexusmods/nexus-api": "catalog:",
"@vortex/nexus-api-v3": "workspace:*",
"@types/lodash": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:",
Expand Down
48 changes: 40 additions & 8 deletions extensions/collections/src/collectionExport.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as path from "path";

import { ICreateCollectionResult, IGraphErrorDetail } from "@nexusmods/nexus-api";
import { V3ApiError } from "@vortex/nexus-api-v3";
import Bluebird from "bluebird";
import * as _ from "lodash";
import Zip from "node-7z";
Expand Down Expand Up @@ -246,7 +247,9 @@ export async function doExportToAPI(
const state: types.IState = api.store.getState();
const mod = state.persistent.mods[gameId][modId];

const { progress, progressEnd } = makeProgressFunction(api);
const { progress, progressEnd, signal } = makeProgressFunction(api, {
cancellable: true,
});

const errors: Array<{ message: string; replace: any }> = [];

Expand Down Expand Up @@ -292,17 +295,17 @@ export async function doExportToAPI(
collectionId = undefined;
}
const result: ICreateCollectionResult = await util.toPromise((cb) =>
api.events.emit("submit-collection", filterInfo(info), filePath, collectionId, cb),
api.events.emit("submit-collection", filterInfo(info), filePath, collectionId, signal, cb),
);
collectionId = result.collection.id;
collectionSlug = result.collection.slug;
// V3 revision endpoint omits slug (it never changes), so fall back to
// the previously stored slug on the mod.
collectionSlug = result.collection.slug ?? mod.attributes?.collectionSlug;
api.store.dispatch(actions.setModAttribute(gameId, modId, "collectionId", collectionId));
api.store.dispatch(
actions.setModAttribute(gameId, modId, "collectionSlug", result.collection.slug),
);
api.store.dispatch(actions.setModAttribute(gameId, modId, "collectionSlug", collectionSlug));
api.store.dispatch(actions.setModAttribute(gameId, modId, "source", "nexus"));
const revisionId = result.revision?.id ?? result["revisionId"];
revisionNumber = result.revision?.revisionNumber ?? result["revisionNumber"];
const revisionId = result.revision?.id;
revisionNumber = result.revision?.revisionNumber;
api.store.dispatch(actions.setModAttribute(gameId, modId, "revisionId", revisionId));
api.store.dispatch(actions.setModAttribute(gameId, modId, "revisionNumber", revisionNumber));
api.store.dispatch(
Expand All @@ -326,6 +329,9 @@ export async function doExportToAPI(
progressEnd();
} catch (err) {
progressEnd();
if (err instanceof util.UserCanceled) {
throw err;
}
if (err.name === "ModFileNotFound") {
const file = info.mods.find((iter) => iter.source.fileId === err.fileId);
api.sendNotification({
Expand All @@ -343,6 +349,32 @@ export async function doExportToAPI(
message: err.message || "<No reason given>",
});
throw new util.ProcessCanceled("collection rejected");
} else if (err instanceof V3ApiError) {
const message = err.detail || err.message;
const validationErrors = err.validationErrors ?? [];
api.sendNotification({
type: "error",
message: "The server rejected this collection",
actions: [
{
title: "More",
action: () => {
api.showDialog(
"error",
"The server rejected this collection",
{
text:
validationErrors.length === 0
? message
: validationErrors.map((ve) => `${ve.pointer}: ${ve.detail}`).join("\n"),
},
[{ label: "Close" }],
);
},
},
],
});
throw new util.ProcessCanceled("collection rejected");
} else if (err.constructor.name === "GraphError") {
const message: string = err.message;
const details: IGraphErrorDetail[] = err["details"] ?? [];
Expand Down
23 changes: 21 additions & 2 deletions extensions/collections/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,30 @@ export function hasEditPermissions(permissions: ICollectionPermission[]): boolea
return allPermissions.includes("collection:edit");
}

export function makeProgressFunction(api: types.IExtensionApi) {
interface IProgressOptions {
cancellable?: boolean;
}

export function makeProgressFunction(api: types.IExtensionApi, options: IProgressOptions = {}) {
const controller = new AbortController();

const cancelAction = options.cancellable
? [
{
title: "Cancel",
action: () => {
controller.abort(new util.UserCanceled());
},
},
]
: undefined;

const notificationId = api.sendNotification({
type: "activity",
title: "Building Collection",
message: "",
progress: 0,
actions: cancelAction,
});

let notiPerc = 0;
Expand Down Expand Up @@ -69,6 +87,7 @@ export function makeProgressFunction(api: types.IExtensionApi) {
title: "Building Collection",
progress: notiPerc,
message: notiText,
actions: cancelAction,
});
}
};
Expand All @@ -77,7 +96,7 @@ export function makeProgressFunction(api: types.IExtensionApi) {
api.dismissNotification(notificationId);
};

return { progress, progressEnd };
return { progress, progressEnd, signal: controller.signal };
}

export function bbProm<T>(func: (...args: any[]) => Promise<T>): (...args: any[]) => Bluebird<T> {
Expand Down
2 changes: 2 additions & 0 deletions packages/nexus-api-v3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
src/generated/
34 changes: 34 additions & 0 deletions packages/nexus-api-v3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://www.schemastore.org/package.json",
"name": "@vortex/nexus-api-v3",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"codegen": "openapi-typescript https://api.nexusmods.com/openapi.yaml -o src/generated/nexus-api-v3.d.ts",
"build": "pnpm codegen && pnpm tsdown",
"dist": "pnpm tsdown",
"test": "vitest run",
"typecheck": "pnpm tsc --noEmit -p tsconfig.json",
"postinstall": "pnpm codegen"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.cts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
},
"dependencies": {
"openapi-fetch": "catalog:"
},
"devDependencies": {
"openapi-typescript": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
Loading
Loading