Skip to content

Commit 334a9e4

Browse files
ErikVeland-NewsErikVeland
authored andcommitted
Stable: Working on extension installation pipeline
1 parent 11afae1 commit 334a9e4

File tree

20 files changed

+719
-124
lines changed

20 files changed

+719
-124
lines changed

app/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
"diskusage": "TanninOne/node-diskusage",
3737
"dnd-core": "^9.4.0",
3838
"draggabilly": "^2.2.0",
39-
"drivelist": "TanninOne/drivelist",
4039
"electron-context-menu": "^3.6.1",
4140
"electron-redux": "file:../vendor/electron-redux-compat",
4241
"electron-updater": "^4.2.0",
@@ -126,4 +125,4 @@
126125
"**/electron-redux": "file:../vendor/electron-redux-compat",
127126
"**/fomod-installer": "file:../vendor/fomod-installer-mac"
128127
}
129-
}
128+
}

app/package.json.backup

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
{
2+
"name": "vortex",
3+
"version": "1.15.0-beta.1",
4+
"productName": "Vortex",
5+
"description": "Vortex",
6+
"author": "Black Tree Gaming Ltd.",
7+
"license": "GPL-3.0",
8+
"main": "main.js",
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/Nexus-Mods/Vortex"
12+
},
13+
"optionalDependencies": {
14+
"crash-dump": "Nexus-Mods/node-crash-dump",
15+
"fomod-installer": "file:vendor/fomod-installer-mac",
16+
"vortexmt": "file:vendor/vortexmt-js",
17+
"winapi-bindings": "Nexus-Mods/node-winapi-bindings"
18+
},
19+
"dependencies": {
20+
"@electron/remote": "^2.0.12",
21+
"cheerio": "1.0.0-rc.12",
22+
"bsdiff-node": "file:vendor/bsdiff-node-js",
23+
"@msgpack/msgpack": "^2.7.0",
24+
"@nexusmods/nexus-api": "Nexus-Mods/node-nexus-api#master",
25+
"bbcode-to-react": "^0.2.9",
26+
"big.js": "^5.2.2",
27+
"bluebird": "^3.7.2",
28+
"bootstrap-sass": "^3.4.1",
29+
"classnames": "^2.5.1",
30+
"commander": "^4.0.1",
31+
"content-disposition": "^0.5.3",
32+
"content-type": "^1.0.5",
33+
"dayjs": "^1.11.13",
34+
"d3": "^5.14.1",
35+
"date-fns": "^2.30.0",
36+
"diskusage": "TanninOne/node-diskusage",
37+
"dnd-core": "^9.4.0",
38+
"draggabilly": "^2.2.0",
39+
"drivelist": "TanninOne/drivelist",
40+
"electron-context-menu": "^3.6.1",
41+
"electron-redux": "file:vendor/electron-redux-compat",
42+
"electron-updater": "^4.2.0",
43+
"encoding-down": "^6.3.0",
44+
"exe-version": "Nexus-Mods/node-exe-version#master",
45+
"feedparser": "^2.2.9",
46+
"fs-extra": "^9.1.0",
47+
"fuzzball": "^1.3.0",
48+
"graphlib": "^2.1.7",
49+
"i18next": "^19.0.1",
50+
"i18next-fs-backend": "^2.6.0",
51+
"iconv-lite": "^0.5.0",
52+
"immutability-helper": "^3.0.1",
53+
"interweave": "^12.9.0",
54+
"is-admin": "^3.0.0",
55+
"json-socket": "foi/node-json-socket",
56+
"jsonwebtoken": "^9.0.2",
57+
"leveldown": "^5.6.0",
58+
"levelup": "^4.4.0",
59+
"lodash": "^4.17.21",
60+
"memoize-one": "^5.1.1",
61+
"minimatch": "^3.0.5",
62+
"modmeta-db": "Nexus-Mods/modmeta-db#master",
63+
"native-errors": "Nexus-Mods/node-native-errors",
64+
"node-7z": "Nexus-Mods/node-7z",
65+
"normalize-url": "^6.0.1",
66+
"packery": "^2.1.2",
67+
"permissions": "Nexus-Mods/node-permissions#master",
68+
"prop-types": "^15.7.2",
69+
"re-reselect": "^3.4.0",
70+
"re-resizable": "^6.11.2",
71+
"react": "^16.12.0",
72+
"react-bootstrap": "^0.33.0",
73+
"react-datepicker": "^3.3.0",
74+
"react-dnd": "^14.0.5",
75+
"react-dnd-html5-backend": "^14.0.5",
76+
"react-dom": "^16.12.0",
77+
"react-i18next": "^11.11.0",
78+
"react-markdown": "^6.0.2",
79+
"react-overlays": "^1.2.0",
80+
"react-redux": "^7.1.3",
81+
"react-resize-detector": "^4.2.1",
82+
"react-select": "^1.2.1",
83+
"react-sortable-tree": "Nexus-Mods/react-sortable-tree",
84+
"recharts": "^1.8.5",
85+
"redux": "^4.2.1",
86+
"redux-act": "^1.7.7",
87+
"redux-batched-actions": "^0.5.0",
88+
"redux-thunk": "^2.3.0",
89+
"relaxed-json": "^1.0.3",
90+
"reselect": "^4.1.8",
91+
"resolve": "^1.22.10",
92+
"rimraf": "TanninOne/rimraf",
93+
"sass": "1.33.0",
94+
"semver": "^7.7.2",
95+
"shortid": "2.2.8",
96+
"simple-vdf": "Nexus-Mods/vdf-parser",
97+
"source-map-support": "^0.5.16",
98+
"string-template": "^1.0.0",
99+
"tmp": "^0.1.0",
100+
"turbowalk": "https://codeload.github.com/Nexus-Mods/node-turbowalk/tar.gz/314f2cdb904a9a075c35261e8a1de10b0af20295",
101+
"universal-analytics": "^0.4.23",
102+
"uuid": "^3.3.3",
103+
"vortex-parse-ini": "https://codeload.github.com/Nexus-Mods/vortex-parse-ini/tar.gz/46f17dc0ee8f7b74a7034ed883981d8a5fa37d24",
104+
"vortex-run": "file:../src/util/vortex-run",
105+
"wholocks": "https://codeload.github.com/Nexus-Mods/node-wholocks/tar.gz/c23132fd59d702dbeef3558cc631186413d7442f",
106+
"winston": "^2.4.3",
107+
"write-file-atomic": "^3.0.1",
108+
"ws": "^7.5.10",
109+
"xml2js": "^0.5.0",
110+
"xxhash-addon": "^1.3.0"
111+
},
112+
"resolutions": {
113+
"react": "^16.12.0",
114+
"**/react": "^16.12.0",
115+
"react-dom": "^16.12.0",
116+
"**/react-dom": "^16.12.0",
117+
"@types/react": "^16.9.43",
118+
"**/@types/react": "^16.9.43",
119+
"@types/react-dom": "^16.9.4",
120+
"**/@types/react-dom": "^16.9.4",
121+
"node-gyp": "^9.0.0",
122+
"**/dir-compare/minimatch": "3.0.5",
123+
"**/cheerio": "1.0.0-rc.12",
124+
"vortexmt": "file:vendor/vortexmt-js",
125+
"**/bsdiff-node": "file:vendor/bsdiff-node-js",
126+
"**/electron-redux": "file:vendor/electron-redux-compat",
127+
"**/fomod-installer": "file:vendor/fomod-installer-mac"
128+
}
129+
}

checkPackages.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const lockfile = require('@yarnpkg/lockfile');
22
const fs = require('fs');
3+
const path = require('path');
34

45
const develLock = lockfile.parse(fs.readFileSync('yarn.lock', { encoding: 'utf8' })).object;
56
const develPackageJson = JSON.parse(fs.readFileSync('package.json', { encoding: 'utf8' }));
@@ -46,9 +47,39 @@ function checkDevelPackages(develPackage, releasePackage) {
4647
if (releasePackage[pkg] === undefined) {
4748
console.error('❌ Package not referenced in release:', pkg);
4849
valid = false;
49-
} else if (releasePackage[pkg] !== develPackage[pkg]) {
50-
console.error('❌ Referenced version mismatch:', pkg, releasePackage[pkg], 'vs', develPackage[pkg]);
51-
valid = false;
50+
} else {
51+
const devSpec = develPackage[pkg];
52+
const relSpec = releasePackage[pkg];
53+
54+
// Normalize local file dependencies by resolving absolute paths relative to their package roots
55+
if (typeof devSpec === 'string' && typeof relSpec === 'string'
56+
&& devSpec.startsWith('file:') && relSpec.startsWith('file:')) {
57+
// If specs are identical strings, accept immediately
58+
if (devSpec === relSpec) {
59+
return;
60+
}
61+
62+
const devPath = devSpec.replace(/^file:/, '');
63+
const relPath = relSpec.replace(/^file:/, '');
64+
65+
// Resolve release path relative to app/, but if that doesn't exist, fall back to repo root
66+
const devResolved = path.resolve('.', devPath);
67+
let relResolved = path.resolve('app', relPath);
68+
if (!fs.existsSync(relResolved)) {
69+
const altRel = path.resolve('.', relPath);
70+
if (fs.existsSync(altRel)) {
71+
relResolved = altRel;
72+
}
73+
}
74+
75+
if (devResolved !== relResolved) {
76+
console.error('❌ Referenced file path mismatch:', pkg, relSpec, 'vs', devSpec);
77+
valid = false;
78+
}
79+
} else if (relSpec !== devSpec) {
80+
console.error('❌ Referenced version mismatch:', pkg, relSpec, 'vs', devSpec);
81+
valid = false;
82+
}
5283
}
5384
});
5485
return valid;

extensions/gamestore-macappstore/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,5 @@ function main(context: types.IExtensionContext) {
292292
return true;
293293
}
294294

295+
export { MacAppStore };
295296
export default main;

extensions/gamestore-macappstore/test/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('MacAppStore', () => {
3737
(fs.pathExists as jest.Mock).mockReset();
3838
(fs.readdir as jest.Mock).mockReset();
3939
(fs.stat as jest.Mock).mockReset();
40-
(exec as jest.Mock).mockReset();
40+
(exec as unknown as jest.Mock).mockReset();
4141

4242
// Set up environment
4343
process.env.HOME = '/Users/test';
@@ -98,7 +98,7 @@ describe('MacAppStore', () => {
9898
(fs.stat as jest.Mock).mockResolvedValue({ isDirectory: () => true });
9999

100100
// Mock exec to return metadata
101-
(exec as jest.Mock).mockImplementation((command, callback) => {
101+
(exec as unknown as jest.Mock).mockImplementation((command, callback) => {
102102
if (command.includes('Civilization VI.app')) {
103103
callback(null, { stdout: 'Civilization VI\ncom.aspyr.civ6\nGame\n' });
104104
} else if (command.includes('World of Warcraft.app')) {

extensions/gamestore-ubisoft/src/index.ts

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as Bluebird from 'bluebird';
21
import * as path from 'path';
32
import * as fs from 'fs-extra';
43

@@ -18,13 +17,13 @@ class UbisoftLauncher implements types.IGameStore {
1817
public name: string = STORE_NAME;
1918
public priority: number = STORE_PRIORITY;
2019
private mClientPath: Promise<string>;
21-
private mCache: Bluebird<types.IGameStoreEntry[]>;
20+
private mCache: Promise<types.IGameStoreEntry[]>;
2221

2322
constructor() {
2423
if (process.platform === 'win32') {
2524
// Windows implementation (existing Uplay functionality)
2625
try {
27-
import('winapi-bindings').then((winapi) => {
26+
Promise.resolve(import('winapi-bindings')).then((winapi) => {
2827
const uplayPath = winapi.RegGetValue('HKEY_LOCAL_MACHINE',
2928
'SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher', 'InstallDir');
3029
this.mClientPath = Promise.resolve(path.join(uplayPath.value as string, 'UbisoftConnect.exe'));
@@ -38,9 +37,9 @@ class UbisoftLauncher implements types.IGameStore {
3837
}
3938
} else if (process.platform === 'darwin') {
4039
// macOS implementation
41-
this.mClientPath = this.findMacOSUbisoftPath().catch((err) => {
40+
this.mClientPath = Promise.resolve(this.findMacOSUbisoftPath()).catch((err) => {
4241
log('info', 'Ubisoft launcher not found on macOS', { error: err.message });
43-
return Promise.resolve(undefined);
42+
return undefined;
4443
});
4544
} else {
4645
log('info', 'Ubisoft launcher not found', { error: 'unsupported platform' });
@@ -51,7 +50,7 @@ class UbisoftLauncher implements types.IGameStore {
5150
/**
5251
* Find Ubisoft Connect on macOS
5352
*/
54-
private async findMacOSUbisoftPath(): Promise<string> {
53+
private async findMacOSUbisoftPath(): globalThis.Promise<string> {
5554
// Check standard installation paths
5655
const possiblePaths = [
5756
'/Applications/Ubisoft Connect.app',
@@ -62,17 +61,17 @@ class UbisoftLauncher implements types.IGameStore {
6261
try {
6362
const stat = await fs.stat(appPath);
6463
if (stat.isDirectory()) {
65-
return Promise.resolve(appPath);
64+
return appPath;
6665
}
6766
} catch (err) {
6867
// Continue to next path
6968
}
7069
}
7170

72-
return Promise.reject(new Error('Ubisoft Connect not found on macOS'));
71+
throw new Error('Ubisoft Connect not found on macOS');
7372
}
7473

75-
public launchGame(appInfo: any, api?: types.IExtensionApi): Bluebird<void> {
74+
public launchGame(appInfo: any, api?: types.IExtensionApi): Promise<void> {
7675
const appId = ((typeof(appInfo) === 'object') && ('appId' in appInfo))
7776
? appInfo.appId : appInfo.toString();
7877

@@ -85,31 +84,31 @@ class UbisoftLauncher implements types.IGameStore {
8584
// need to change this in the future to allow game extensions to choose the executable
8685
// they want to launch.
8786
const posixPath = `ubisoft://launch/${appId}/0`;
88-
return util.opn(posixPath).catch(err => Bluebird.resolve());
87+
return util.opn(posixPath).catch(() => Promise.resolve());
8988
}
9089

91-
public allGames(): Bluebird<types.IGameStoreEntry[]> {
90+
public allGames(): Promise<types.IGameStoreEntry[]> {
9291
if (this.mCache === undefined) {
9392
this.mCache = this.getGameEntries();
9493
}
9594
return this.mCache;
9695
}
9796

98-
public reloadGames(): Bluebird<void> {
97+
public reloadGames(): Promise<void> {
9998
this.mCache = this.getGameEntries();
100-
return Bluebird.resolve();
99+
return Promise.resolve();
101100
}
102101

103-
public findByName(appName: string): Bluebird<types.IGameStoreEntry> {
102+
public findByName(appName: string): Promise<types.IGameStoreEntry> {
104103
const re = new RegExp('^' + appName + '$');
105104
return this.allGames()
106105
.then(entries => entries.find(entry => re.test(entry.name)))
107106
.then(entry => (entry === undefined)
108-
? Bluebird.reject(new types.GameEntryNotFound(appName, STORE_ID))
109-
: Bluebird.resolve(entry));
107+
? Promise.reject(new types.GameEntryNotFound(appName, STORE_ID))
108+
: Promise.resolve(entry));
110109
}
111110

112-
public findByAppId(appId: string | string[]): Bluebird<types.IGameStoreEntry> {
111+
public findByAppId(appId: string | string[]): Promise<types.IGameStoreEntry> {
113112
const matcher = Array.isArray(appId)
114113
? (entry: types.IGameStoreEntry) => (appId.includes(entry.appid))
115114
: (entry: types.IGameStoreEntry) => (appId === entry.appid);
@@ -118,33 +117,33 @@ class UbisoftLauncher implements types.IGameStore {
118117
.then(entries => {
119118
const gameEntry = entries.find(matcher);
120119
if (gameEntry === undefined) {
121-
return Bluebird.reject(
120+
return Promise.reject(
122121
new types.GameEntryNotFound(Array.isArray(appId) ? appId.join(', ') : appId, STORE_ID));
123122
} else {
124-
return Bluebird.resolve(gameEntry);
123+
return Promise.resolve(gameEntry);
125124
}
126125
});
127126
}
128127

129-
public getGameStorePath(): Bluebird<string> {
128+
public getGameStorePath(): Promise<string> {
130129
return (!!this.mClientPath)
131-
? Bluebird.resolve(this.mClientPath).then(x => x)
132-
: Bluebird.resolve(undefined);
130+
? Promise.resolve(this.mClientPath).then(x => x)
131+
: Promise.resolve(undefined);
133132
}
134133

135-
private getGameEntries(): Bluebird<types.IGameStoreEntry[]> {
134+
private getGameEntries(): Promise<types.IGameStoreEntry[]> {
136135
if (process.platform === 'win32') {
137-
return Bluebird.resolve(this.getGameEntriesWindows()).then(x => x);
136+
return Promise.resolve(this.getGameEntriesWindows()).then(x => x);
138137
} else if (process.platform === 'darwin') {
139-
return Bluebird.resolve(this.getGameEntriesMacOS()).then(x => x);
138+
return Promise.resolve(this.getGameEntriesMacOS()).then(x => x);
140139
} else {
141-
return Bluebird.resolve([]);
140+
return Promise.resolve([]);
142141
}
143142
}
144143

145144
private getGameEntriesWindows(): Promise<types.IGameStoreEntry[]> {
146145
return (!!this.mClientPath)
147-
? import('winapi-bindings').then((winapi) => {
146+
? Promise.resolve(import('winapi-bindings')).then((winapi) => {
148147
return new Promise<types.IGameStoreEntry[]>((resolve, reject) => {
149148
try {
150149
winapi.WithRegOpen('HKEY_LOCAL_MACHINE',
@@ -185,7 +184,7 @@ class UbisoftLauncher implements types.IGameStore {
185184
: Promise.resolve([]);
186185
}
187186

188-
private async getGameEntriesMacOS(): Promise<types.IGameStoreEntry[]> {
187+
private async getGameEntriesMacOS(): globalThis.Promise<types.IGameStoreEntry[]> {
189188
try {
190189
// On macOS, Ubisoft Connect stores data in ~/Library/Application Support/Ubisoft/Ubisoft Game Launcher
191190
const homeDir = process.env.HOME || '';
@@ -278,7 +277,7 @@ class UbisoftLauncher implements types.IGameStore {
278277
return gameNames[gameId] || `Ubisoft Game ${gameId}`;
279278
}
280279

281-
private async findGameInstallationPath(gameId: string): Promise<string | null> {
280+
private async findGameInstallationPath(gameId: string): globalThis.Promise<string | null> {
282281
try {
283282
// Check common installation paths
284283
const homeDir = process.env.HOME || '';
@@ -338,4 +337,5 @@ function main(context: types.IExtensionContext) {
338337
return true;
339338
}
340339

340+
export { UbisoftLauncher };
341341
export default main;

jest.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)