@cursorpool-dev/cli 0.5.9 → 0.5.11
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/node_modules/@cursor-pool/extension/dist/extension.js +1 -1
- package/node_modules/@cursor-pool/extension/package.json +3 -3
- package/node_modules/@cursor-pool/extension/src/api.ts +1 -1
- package/node_modules/@cursor-pool/extension/test/panel.test.ts +1 -1
- package/node_modules/@cursor-pool/patcher/package.json +2 -2
- package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +28 -8
- package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +11 -0
- package/node_modules/@cursor-pool/service/package.json +2 -2
- package/node_modules/@cursor-pool/shared/package.json +1 -1
- package/node_modules/@cursor-pool/shared/test/manifest.test.ts +6 -6
- package/node_modules/@esbuild/linux-arm64/README.md +3 -0
- package/node_modules/@esbuild/linux-arm64/bin/esbuild +0 -0
- package/node_modules/@esbuild/linux-arm64/package.json +20 -0
- package/package.json +5 -5
- package/src/compat.ts +94 -15
- package/src/cursor.ts +45 -4
- package/src/extensionBundle.ts +1 -1
- package/src/extensionLink.ts +2 -2
- package/src/install.ts +2 -1
- package/src/patchSet.ts +46 -4
- package/src/platform.ts +3 -3
- package/src/repair.ts +9 -5
- package/src/restore.ts +13 -6
- package/src/status.ts +5 -3
- package/src/target.ts +12 -0
- package/src/uninstall.ts +6 -2
- package/test/compat.test.ts +90 -0
- package/test/cursor.test.ts +54 -0
- package/test/e2e-install.test.ts +36 -0
- package/test/patchSet.test.ts +71 -0
- package/test/repair.test.ts +131 -0
- package/test/restore.test.ts +59 -3
- package/test/target.test.ts +28 -0
package/src/patchSet.ts
CHANGED
|
@@ -54,13 +54,43 @@ const LINUX_WORKBENCH_RELATIVE_PATH =
|
|
|
54
54
|
'usr/share/cursor/resources/app/out/vs/workbench/workbench.desktop.main.js';
|
|
55
55
|
const LINUX_ALWAYS_LOCAL_RELATIVE_PATH =
|
|
56
56
|
'usr/share/cursor/resources/app/extensions/cursor-always-local/dist/main.js';
|
|
57
|
+
const LINUX_DEB_WORKBENCH_RELATIVE_PATH =
|
|
58
|
+
'resources/app/out/vs/workbench/workbench.desktop.main.js';
|
|
59
|
+
const LINUX_DEB_ALWAYS_LOCAL_RELATIVE_PATH =
|
|
60
|
+
'resources/app/extensions/cursor-always-local/dist/main.js';
|
|
57
61
|
|
|
58
|
-
function
|
|
59
|
-
return options.
|
|
62
|
+
function linuxUsesDebRoot(options: { agentExecTargetRelativePath?: string }) {
|
|
63
|
+
return options.agentExecTargetRelativePath?.startsWith('resources/app/') ?? false;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
function
|
|
63
|
-
|
|
66
|
+
function workbenchRelativePath(options: {
|
|
67
|
+
platform?: NodeJS.Platform;
|
|
68
|
+
agentExecTargetRelativePath?: string;
|
|
69
|
+
workbenchTargetRelativePath?: string;
|
|
70
|
+
}) {
|
|
71
|
+
if (options.workbenchTargetRelativePath) {
|
|
72
|
+
return options.workbenchTargetRelativePath;
|
|
73
|
+
}
|
|
74
|
+
if (options.platform === 'linux') {
|
|
75
|
+
return linuxUsesDebRoot(options) ? LINUX_DEB_WORKBENCH_RELATIVE_PATH : LINUX_WORKBENCH_RELATIVE_PATH;
|
|
76
|
+
}
|
|
77
|
+
return CURSOR_WORKBENCH_RELATIVE_PATH;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function alwaysLocalRelativePath(options: {
|
|
81
|
+
platform?: NodeJS.Platform;
|
|
82
|
+
agentExecTargetRelativePath?: string;
|
|
83
|
+
alwaysLocalTargetRelativePath?: string;
|
|
84
|
+
}) {
|
|
85
|
+
if (options.alwaysLocalTargetRelativePath) {
|
|
86
|
+
return options.alwaysLocalTargetRelativePath;
|
|
87
|
+
}
|
|
88
|
+
if (options.platform === 'linux') {
|
|
89
|
+
return linuxUsesDebRoot(options)
|
|
90
|
+
? LINUX_DEB_ALWAYS_LOCAL_RELATIVE_PATH
|
|
91
|
+
: LINUX_ALWAYS_LOCAL_RELATIVE_PATH;
|
|
92
|
+
}
|
|
93
|
+
return CURSOR_ALWAYS_LOCAL_RELATIVE_PATH;
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
async function fileContainsMarker(
|
|
@@ -143,6 +173,11 @@ export async function patchCursorSet(appPath: string, options: PatchCursorSetOpt
|
|
|
143
173
|
}
|
|
144
174
|
|
|
145
175
|
export async function restoreCursorSet(appPath: string, options: RestoreCursorSetOptions = {}) {
|
|
176
|
+
const before = await readCursorPatchSetState(appPath, {
|
|
177
|
+
agentExecTargetRelativePath: options.agentExecTargetRelativePath,
|
|
178
|
+
platform: options.platform,
|
|
179
|
+
workbenchTargetRelativePath: options.workbenchTargetRelativePath,
|
|
180
|
+
});
|
|
146
181
|
const restoreErrors: unknown[] = [];
|
|
147
182
|
const restore = async (operation: () => Promise<unknown>) => {
|
|
148
183
|
try {
|
|
@@ -173,10 +208,17 @@ export async function restoreCursorSet(appPath: string, options: RestoreCursorSe
|
|
|
173
208
|
await restore(() =>
|
|
174
209
|
(options.restoreCursorAgentExec ?? restoreCursorAgentExec)(appPath, {
|
|
175
210
|
backupDir: options.backupDir,
|
|
211
|
+
targetRelativePath: options.agentExecTargetRelativePath,
|
|
176
212
|
}),
|
|
177
213
|
);
|
|
178
214
|
|
|
179
215
|
if (restoreErrors.length > 0) {
|
|
180
216
|
throw new AggregateError(restoreErrors, 'Failed to restore one or more Cursor patches.');
|
|
181
217
|
}
|
|
218
|
+
const after = await readCursorPatchSetState(appPath, {
|
|
219
|
+
agentExecTargetRelativePath: options.agentExecTargetRelativePath,
|
|
220
|
+
platform: options.platform,
|
|
221
|
+
workbenchTargetRelativePath: options.workbenchTargetRelativePath,
|
|
222
|
+
});
|
|
223
|
+
return { before, after };
|
|
182
224
|
}
|
package/src/platform.ts
CHANGED
|
@@ -82,9 +82,9 @@ function buildDeviceInfo() {
|
|
|
82
82
|
name: hostname(),
|
|
83
83
|
os: osPlatform(),
|
|
84
84
|
arch: arch(),
|
|
85
|
-
cliVersion: '0.5.
|
|
86
|
-
serviceVersion: '0.5.
|
|
87
|
-
extensionVersion: '0.5.
|
|
85
|
+
cliVersion: '0.5.11',
|
|
86
|
+
serviceVersion: '0.5.11',
|
|
87
|
+
extensionVersion: '0.5.11',
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
package/src/repair.ts
CHANGED
|
@@ -69,7 +69,7 @@ export type RepairOptions = FindCursorOptions &
|
|
|
69
69
|
adHocResignApp?: typeof adHocResignApp;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const PACKAGE_VERSION = '0.5.
|
|
72
|
+
const PACKAGE_VERSION = '0.5.11';
|
|
73
73
|
|
|
74
74
|
function normalizeAppPath(path: string) {
|
|
75
75
|
return normalize(path).replace(/\/+$/, '');
|
|
@@ -110,12 +110,14 @@ function resolveRepairCompatEntry({
|
|
|
110
110
|
entries?: CompatibilityManifestEntry[];
|
|
111
111
|
}) {
|
|
112
112
|
const compatEntries = entries ?? DEFAULT_COMPAT_ENTRIES;
|
|
113
|
+
const versionFamily = cursorVersion.match(/^(\d+\.\d+)(?:\.|$)/)?.[1];
|
|
113
114
|
const entry = compatEntries.find(
|
|
114
115
|
(candidate) =>
|
|
115
116
|
candidate.platform === platform &&
|
|
116
117
|
candidate.arch === arch &&
|
|
117
|
-
candidate.cursorVersion === cursorVersion
|
|
118
|
-
|
|
118
|
+
(candidate.cursorVersion === cursorVersion ||
|
|
119
|
+
candidate.cursorVersion === versionFamily) &&
|
|
120
|
+
(candidate.cursorCommit === '*' || candidate.cursorCommit === cursorCommit),
|
|
119
121
|
);
|
|
120
122
|
|
|
121
123
|
if (!entry) {
|
|
@@ -196,13 +198,13 @@ async function maybeAdHocResign({
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
export async function repair(options: RepairOptions = {}) {
|
|
199
|
-
const
|
|
201
|
+
const environment = detectEnvironment(options);
|
|
202
|
+
const target = resolveCursorTarget({ ...options, platform: environment.platform });
|
|
200
203
|
if (target.mode !== 'real') {
|
|
201
204
|
throw new Error('repair is only supported for real Cursor installs in MVP-1');
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
const cursor = await findCursor({ ...options, appPath: target.appPath });
|
|
205
|
-
const environment = detectEnvironment(options);
|
|
206
208
|
const compatEntries = await loadCompatEntries({
|
|
207
209
|
compatEntries: options.compatEntries,
|
|
208
210
|
apiBaseUrl: options.apiBaseUrl,
|
|
@@ -323,6 +325,7 @@ export async function repair(options: RepairOptions = {}) {
|
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
const patchSetState = await readCursorPatchSetState(cursor.appPath, {
|
|
328
|
+
platform: environment.platform,
|
|
326
329
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
327
330
|
});
|
|
328
331
|
|
|
@@ -332,6 +335,7 @@ export async function repair(options: RepairOptions = {}) {
|
|
|
332
335
|
} else {
|
|
333
336
|
const repairedPatchSet = await patchCursorSet(cursor.appPath, {
|
|
334
337
|
backupDir,
|
|
338
|
+
platform: environment.platform,
|
|
335
339
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
336
340
|
patchCursorAgentExec: options.patchCursorAgentExec,
|
|
337
341
|
patchCursorWorkbenchAuthGate: options.patchCursorWorkbenchAuthGate,
|
package/src/restore.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalize } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCursorAgentExecPath } from '@cursor-pool/patcher';
|
|
3
3
|
import { DEFAULT_RUNTIME_FILE } from '@cursor-pool/shared/runtime';
|
|
4
4
|
import {
|
|
5
5
|
confirmRealOperation,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ConfirmRealOperationOptions,
|
|
8
8
|
} from './confirm';
|
|
9
9
|
import { findCursor, type FindCursorOptions } from './cursor';
|
|
10
|
+
import { detectEnvironment, type DetectEnvironmentOptions } from './environment';
|
|
10
11
|
import {
|
|
11
12
|
installIdForAppPath,
|
|
12
13
|
readInstallRecord,
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
import { stopRuntimeService } from './serviceProcess';
|
|
17
18
|
import { resolveCursorTarget, type CursorTargetOptions } from './target';
|
|
18
19
|
import { assertDisposableCursorAppPath, readTrialRecord, type TrialRecordOptions } from './trial';
|
|
20
|
+
import { formatCursorPatchSetState, restoreCursorSet } from './patchSet';
|
|
19
21
|
|
|
20
22
|
export type RestoreOptions = FindCursorOptions &
|
|
23
|
+
DetectEnvironmentOptions &
|
|
21
24
|
CursorTargetOptions &
|
|
22
25
|
TrialRecordOptions &
|
|
23
26
|
InstallRecordOptions & {
|
|
@@ -85,9 +88,12 @@ function formatRestoreConfirmation({
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
export async function restore(options: RestoreOptions = {}) {
|
|
88
|
-
const
|
|
91
|
+
const environment = detectEnvironment(options);
|
|
92
|
+
const target = resolveCursorTarget({ ...options, platform: environment.platform });
|
|
89
93
|
const appPath =
|
|
90
|
-
target.mode === 'disposable'
|
|
94
|
+
target.mode === 'disposable'
|
|
95
|
+
? assertDisposableCursorAppPath(target.appPath, { platform: environment.platform })
|
|
96
|
+
: target.appPath;
|
|
91
97
|
const cursor = await findCursor({ ...options, appPath });
|
|
92
98
|
const installRecord =
|
|
93
99
|
target.mode === 'real'
|
|
@@ -132,9 +138,10 @@ export async function restore(options: RestoreOptions = {}) {
|
|
|
132
138
|
});
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
const result = await
|
|
141
|
+
const result = await restoreCursorSet(cursor.appPath, {
|
|
136
142
|
backupDir,
|
|
137
|
-
targetRelativePath,
|
|
143
|
+
agentExecTargetRelativePath: targetRelativePath,
|
|
144
|
+
platform: environment.platform,
|
|
138
145
|
});
|
|
139
146
|
if (target.mode === 'real') {
|
|
140
147
|
await stopRuntimeServiceIfRecorded(runtimeFile);
|
|
@@ -145,7 +152,7 @@ export async function restore(options: RestoreOptions = {}) {
|
|
|
145
152
|
`mode: ${target.mode}`,
|
|
146
153
|
`app: ${cursor.appPath}`,
|
|
147
154
|
'restore: ok',
|
|
148
|
-
`patch: ${result.
|
|
155
|
+
`patch: ${formatCursorPatchSetState(result.after)}`,
|
|
149
156
|
target.mode === 'real'
|
|
150
157
|
? `install-record: ${realInstallRecord ? 'recorded' : 'missing'}`
|
|
151
158
|
: `trial: ${trialRecord ? 'recorded' : 'missing'}`,
|
package/src/status.ts
CHANGED
|
@@ -146,10 +146,12 @@ async function getTakeoverStatus(
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export async function status(options: StatusOptions = {}) {
|
|
149
|
-
const target = resolveCursorTarget(options);
|
|
150
|
-
const appPath =
|
|
151
|
-
target.mode === 'disposable' ? assertDisposableCursorAppPath(target.appPath) : target.appPath;
|
|
152
149
|
const environment = detectEnvironment(options);
|
|
150
|
+
const target = resolveCursorTarget({ ...options, platform: environment.platform });
|
|
151
|
+
const appPath =
|
|
152
|
+
target.mode === 'disposable'
|
|
153
|
+
? assertDisposableCursorAppPath(target.appPath, { platform: environment.platform })
|
|
154
|
+
: target.appPath;
|
|
153
155
|
const cursor = await findCursor({ ...options, appPath });
|
|
154
156
|
const compatEntries = await loadCompatEntries({
|
|
155
157
|
compatEntries: options.compatEntries,
|
package/src/target.ts
CHANGED
|
@@ -20,8 +20,20 @@ export type CursorTarget =
|
|
|
20
20
|
requiresConfirmation: true;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
function isLinuxRealCursorPath(appPath: string, platform: NodeJS.Platform | undefined) {
|
|
24
|
+
return platform === 'linux' && appPath.replace(/\/+$/, '') === '/usr/share/cursor';
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
export function resolveCursorTarget(options: CursorTargetOptions = {}): CursorTarget {
|
|
24
28
|
if (options.appPath) {
|
|
29
|
+
if (isLinuxRealCursorPath(options.appPath, options.platform)) {
|
|
30
|
+
return {
|
|
31
|
+
mode: 'real',
|
|
32
|
+
appPath: options.appPath.replace(/\/+$/, ''),
|
|
33
|
+
requiresConfirmation: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
return {
|
|
26
38
|
mode: 'disposable',
|
|
27
39
|
appPath: assertDisposableCursorAppPath(options.appPath, { platform: options.platform }),
|
package/src/uninstall.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { restore } from './restore';
|
|
|
28
28
|
import type { RestoreOptions } from './restore';
|
|
29
29
|
import { stopRuntimeService } from './serviceProcess';
|
|
30
30
|
import { removeUserAutostart } from './autostart';
|
|
31
|
+
import { detectEnvironment } from './environment';
|
|
31
32
|
import { resolveCursorTarget, type CursorTargetOptions } from './target';
|
|
32
33
|
import {
|
|
33
34
|
assertDisposableCursorAppPath,
|
|
@@ -107,9 +108,12 @@ async function removeRecordedLinkedExtensionBundle(
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
export async function uninstall(options: UninstallOptions = {}) {
|
|
110
|
-
const
|
|
111
|
+
const environment = detectEnvironment(options);
|
|
112
|
+
const target = resolveCursorTarget({ ...options, platform: environment.platform });
|
|
111
113
|
const appPath =
|
|
112
|
-
target.mode === 'disposable'
|
|
114
|
+
target.mode === 'disposable'
|
|
115
|
+
? assertDisposableCursorAppPath(target.appPath, { platform: environment.platform })
|
|
116
|
+
: target.appPath;
|
|
113
117
|
const cursor = await findCursor({ ...options, appPath });
|
|
114
118
|
const installRecord =
|
|
115
119
|
target.mode === 'real'
|
package/test/compat.test.ts
CHANGED
|
@@ -177,6 +177,96 @@ test('default compatibility manifest supports Cursor 3.6.31 Linux x64 AppImage',
|
|
|
177
177
|
);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
+
test('default compatibility manifest supports Cursor 3.7.12 Linux arm64 deb installs', () => {
|
|
181
|
+
const entry = resolveCompatEntry(
|
|
182
|
+
{
|
|
183
|
+
appPath: '/usr/share/cursor',
|
|
184
|
+
version: '3.7.12',
|
|
185
|
+
commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
platform: 'linux',
|
|
189
|
+
arch: 'arm64',
|
|
190
|
+
nodeVersion: process.version,
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
195
|
+
assert.equal(entry.requiresAdHocResign, false);
|
|
196
|
+
assert.equal(
|
|
197
|
+
entry.targetRelativePath,
|
|
198
|
+
'resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
199
|
+
);
|
|
200
|
+
assert.equal(
|
|
201
|
+
entry.expectedSha256,
|
|
202
|
+
'9ce7a2f40a98a27eb1b609a79e0e1707bad5fbb02493693f6f18945a7640dde4',
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('default compatibility manifest supports verified Cursor 3.4 Linux arm64 deb installs', () => {
|
|
207
|
+
const entry = resolveCompatEntry(
|
|
208
|
+
{
|
|
209
|
+
appPath: '/usr/share/cursor',
|
|
210
|
+
version: '3.4.20',
|
|
211
|
+
commit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
platform: 'linux',
|
|
215
|
+
arch: 'arm64',
|
|
216
|
+
nodeVersion: process.version,
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
221
|
+
assert.equal(entry.requiresAdHocResign, false);
|
|
222
|
+
assert.equal(
|
|
223
|
+
entry.targetRelativePath,
|
|
224
|
+
'resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
225
|
+
);
|
|
226
|
+
assert.equal(
|
|
227
|
+
entry.expectedSha256,
|
|
228
|
+
'4ccb7526e5ece8f6a98a99724a048977698cbf52cb2033ec06f2b5a0a085fe71',
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('default compatibility manifest supports verified Cursor 3.5 and 3.6 Linux arm64 deb installs', () => {
|
|
233
|
+
const cases = [
|
|
234
|
+
{
|
|
235
|
+
version: '3.5.38',
|
|
236
|
+
commit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
|
|
237
|
+
expectedSha256: 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
version: '3.6.31',
|
|
241
|
+
commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
|
|
242
|
+
expectedSha256: '05bfa29eacb8271c378765ead4bf881f806b97549dd13367183aa7a9331c1131',
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
for (const fixture of cases) {
|
|
247
|
+
const entry = resolveCompatEntry(
|
|
248
|
+
{
|
|
249
|
+
appPath: '/usr/share/cursor',
|
|
250
|
+
version: fixture.version,
|
|
251
|
+
commit: fixture.commit,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
platform: 'linux',
|
|
255
|
+
arch: 'arm64',
|
|
256
|
+
nodeVersion: process.version,
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
261
|
+
assert.equal(entry.requiresAdHocResign, false);
|
|
262
|
+
assert.equal(
|
|
263
|
+
entry.targetRelativePath,
|
|
264
|
+
'resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
265
|
+
);
|
|
266
|
+
assert.equal(entry.expectedSha256, fixture.expectedSha256);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
180
270
|
test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Rosetta-launched installers', () => {
|
|
181
271
|
const entry = resolveCompatEntry(
|
|
182
272
|
{
|
package/test/cursor.test.ts
CHANGED
|
@@ -22,6 +22,60 @@ test('defaultCursorAppPath gives a helpful Windows error when LOCALAPPDATA is un
|
|
|
22
22
|
);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
test('defaultCursorAppPath auto-detects Linux deb installs from the cursor launcher on PATH', () => {
|
|
26
|
+
assert.equal(
|
|
27
|
+
defaultCursorAppPath({
|
|
28
|
+
platform: 'linux',
|
|
29
|
+
execFileSync: (command, args) => {
|
|
30
|
+
if (command === 'which' && args[0] === 'cursor') {
|
|
31
|
+
return '/usr/bin/cursor\n';
|
|
32
|
+
}
|
|
33
|
+
if (command === 'readlink' && args.join(' ') === '-f /usr/bin/cursor') {
|
|
34
|
+
return '/usr/share/cursor/bin/cursor\n';
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`unexpected command: ${command} ${args.join(' ')}`);
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
'/usr/share/cursor',
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('findCursor auto-detects Linux deb installs from the cursor launcher on PATH', async () => {
|
|
44
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-linux-deb-'));
|
|
45
|
+
const appPath = join(tempDir, 'usr/share/cursor');
|
|
46
|
+
const launcherPath = join(appPath, 'bin/cursor');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await mkdir(join(appPath, 'resources/app'), { recursive: true });
|
|
50
|
+
await mkdir(join(appPath, 'bin'), { recursive: true });
|
|
51
|
+
await writeFile(launcherPath, '#!/bin/sh\n', 'utf8');
|
|
52
|
+
await writeFile(
|
|
53
|
+
join(appPath, 'resources/app/product.json'),
|
|
54
|
+
JSON.stringify({ version: '3.7.12', commit: 'linux-arm64-commit' }),
|
|
55
|
+
'utf8',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const cursor = await findCursor({
|
|
59
|
+
platform: 'linux',
|
|
60
|
+
execFile: async (command, args) => {
|
|
61
|
+
if (command === 'which' && args[0] === 'cursor') {
|
|
62
|
+
return { stdout: '/usr/bin/cursor\n' };
|
|
63
|
+
}
|
|
64
|
+
if (command === 'readlink' && args.join(' ') === '-f /usr/bin/cursor') {
|
|
65
|
+
return { stdout: `${launcherPath}\n` };
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`unexpected command: ${command} ${args.join(' ')}`);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
assert.equal(cursor.appPath, appPath);
|
|
72
|
+
assert.equal(cursor.version, '3.7.12');
|
|
73
|
+
assert.equal(cursor.commit, 'linux-arm64-commit');
|
|
74
|
+
} finally {
|
|
75
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
25
79
|
test('findCursor extracts Linux AppImage files before reading product metadata', async () => {
|
|
26
80
|
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-linux-appimage-'));
|
|
27
81
|
const appImagePath = join(tempDir, 'Cursor.AppImage');
|
package/test/e2e-install.test.ts
CHANGED
|
@@ -32,23 +32,59 @@ const cursorVersion = '3.5.38';
|
|
|
32
32
|
const cursorCommit = '009bb5a3600dd98fe1c1f25798f767f686e14750';
|
|
33
33
|
const targetRelativePath =
|
|
34
34
|
'Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js';
|
|
35
|
+
const alwaysLocalRelativePath =
|
|
36
|
+
'Contents/Resources/app/extensions/cursor-always-local/dist/main.js';
|
|
37
|
+
const workbenchRelativePath =
|
|
38
|
+
'Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js';
|
|
35
39
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
36
40
|
const serviceServerPath = resolve(repoRoot, 'packages/service/src/server.ts');
|
|
41
|
+
const composerAuthGateAnchor =
|
|
42
|
+
'get when(){return p()&&!fzC},get fallback(){return he(DGC,{})}';
|
|
43
|
+
const composerSubmitAuthGateAnchor =
|
|
44
|
+
'if(!p()){e.cursorAuthenticationService.login(),e.commandService.executeCommand(wV,"general");return}';
|
|
45
|
+
const agentLoopRunAnchor =
|
|
46
|
+
'await this.agentClientService.run(te,H,$e,Ne,Ce,T,ze,me,we,[],ct)';
|
|
47
|
+
const buildFlagsLocalModeAnchor = 'localMode:!1';
|
|
48
|
+
const localProviderConfigAnchor =
|
|
49
|
+
'async getLocalAgentProviderConfig(e){const t="[AgentClientService][getLocalAgentProviderConfig]",i=L0.localMode?await this.shellEnvironmentService.getShellEnv():{},r=e?.credentials,s=r?.case==="apiKeyCredentials"?r.value:void 0,o=this.reactiveStorageService.applicationUserPersistentStorage,a=o.useOpenAIKey===!0?this.cursorAuthenticationService.openAIKey()??void 0:void 0,u=xgS({apiKeyCandidates:[{value:s?.apiKey,source:"modelDetails.apiKeyCredentials.apiKey"},{value:a,source:"storage.openAIKey"}],baseUrlCandidates:[{value:s?.baseUrl,source:"modelDetails.apiKeyCredentials.baseUrl"},{value:o.openAIBaseUrl,source:"storage.openAIBaseUrl"}]});return{baseUrl:u.baseUrl,apiKey:u.apiKey}}createDefaultLocalModel(e){return "default"}';
|
|
50
|
+
const agentClientRunLocalModeAnchor =
|
|
51
|
+
'if(L0.localMode){try{g.onNetworkPhaseStart?.()}catch(b){this.logService.warn("[AgentClientService] onNetworkPhaseStart callback failed in local mode",b)}return this.runLocalAgentInExtensionHost(e,t,i,r,s,a,d,h,g)}return this.client.run(e,t,i,r,s,o,a,u,d,h,g)';
|
|
52
|
+
|
|
53
|
+
function workbenchFixture() {
|
|
54
|
+
return [
|
|
55
|
+
`function composer(){return he(Mt,{${composerAuthGateAnchor},get children(){return "controls"}})}`,
|
|
56
|
+
`async function submit(){${composerSubmitAuthGateAnchor};return "submitted";}`,
|
|
57
|
+
`const flags={${buildFlagsLocalModeAnchor}}`,
|
|
58
|
+
localProviderConfigAnchor,
|
|
59
|
+
`async function runAgentLoop(){${agentLoopRunAnchor}}`,
|
|
60
|
+
`async function agentClientRun(){const g={};${agentClientRunLocalModeAnchor}}`,
|
|
61
|
+
].join(';');
|
|
62
|
+
}
|
|
37
63
|
|
|
38
64
|
async function createFixtureApp(prefix: string) {
|
|
39
65
|
const tempDir = await mkdtemp(join(tmpdir(), prefix));
|
|
40
66
|
const appPath = join(tempDir, 'Cursor.app');
|
|
41
67
|
const targetPath = join(appPath, targetRelativePath);
|
|
68
|
+
const alwaysLocalPath = join(appPath, alwaysLocalRelativePath);
|
|
69
|
+
const workbenchPath = join(appPath, workbenchRelativePath);
|
|
42
70
|
const targetContent = `function main() { return "agent"; }\n${CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR}\nmain();\n`;
|
|
43
71
|
await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-agent-exec/dist'), {
|
|
44
72
|
recursive: true,
|
|
45
73
|
});
|
|
74
|
+
await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-always-local/dist'), {
|
|
75
|
+
recursive: true,
|
|
76
|
+
});
|
|
77
|
+
await mkdir(join(appPath, 'Contents/Resources/app/out/vs/workbench'), {
|
|
78
|
+
recursive: true,
|
|
79
|
+
});
|
|
46
80
|
await writeFile(
|
|
47
81
|
join(appPath, 'Contents/Resources/app/product.json'),
|
|
48
82
|
JSON.stringify({ version: cursorVersion, commit: cursorCommit }),
|
|
49
83
|
'utf8',
|
|
50
84
|
);
|
|
51
85
|
await writeFile(targetPath, targetContent, 'utf8');
|
|
86
|
+
await writeFile(alwaysLocalPath, 'function alwaysLocal(){}\n', 'utf8');
|
|
87
|
+
await writeFile(workbenchPath, workbenchFixture(), 'utf8');
|
|
52
88
|
|
|
53
89
|
const originalHash = createHash('sha256').update(targetContent).digest('hex');
|
|
54
90
|
const compatEntry: CompatibilityManifestEntry = {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { readCursorPatchSetState } from '../src/patchSet';
|
|
7
|
+
|
|
8
|
+
async function withLinuxCursorApp(rootKind: 'deb' | 'appimage') {
|
|
9
|
+
const tempDir = await mkdtemp(join(tmpdir(), `cursor-pool-patchset-${rootKind}-`));
|
|
10
|
+
const appPath = rootKind === 'deb' ? join(tempDir, 'cursor') : join(tempDir, 'squashfs-root');
|
|
11
|
+
const resourceRoot =
|
|
12
|
+
rootKind === 'deb' ? join(appPath, 'resources/app') : join(appPath, 'usr/share/cursor/resources/app');
|
|
13
|
+
await mkdir(join(resourceRoot, 'extensions/cursor-agent-exec/dist'), { recursive: true });
|
|
14
|
+
await mkdir(join(resourceRoot, 'out/vs/workbench'), { recursive: true });
|
|
15
|
+
await writeFile(
|
|
16
|
+
join(resourceRoot, 'extensions/cursor-agent-exec/dist/main.js'),
|
|
17
|
+
'function agent(){}\n',
|
|
18
|
+
'utf8',
|
|
19
|
+
);
|
|
20
|
+
await writeFile(
|
|
21
|
+
join(resourceRoot, 'out/vs/workbench/workbench.desktop.main.js'),
|
|
22
|
+
'function workbench(){}\n',
|
|
23
|
+
'utf8',
|
|
24
|
+
);
|
|
25
|
+
return { appPath, tempDir, resourceRoot };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('readCursorPatchSetState resolves Linux deb install patch targets from /usr/share/cursor root', async () => {
|
|
29
|
+
const fixture = await withLinuxCursorApp('deb');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const state = await readCursorPatchSetState(fixture.appPath, {
|
|
33
|
+
platform: 'linux',
|
|
34
|
+
agentExecTargetRelativePath: 'resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
assert.equal(
|
|
38
|
+
state.patches.find((patch) => patch.name === 'agent-exec')?.targetPath,
|
|
39
|
+
join(fixture.resourceRoot, 'extensions/cursor-agent-exec/dist/main.js'),
|
|
40
|
+
);
|
|
41
|
+
assert.equal(
|
|
42
|
+
state.patches.find((patch) => patch.name === 'workbench')?.targetPath,
|
|
43
|
+
join(fixture.resourceRoot, 'out/vs/workbench/workbench.desktop.main.js'),
|
|
44
|
+
);
|
|
45
|
+
} finally {
|
|
46
|
+
await rm(fixture.tempDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('readCursorPatchSetState keeps Linux AppImage squashfs-root patch targets unchanged', async () => {
|
|
51
|
+
const fixture = await withLinuxCursorApp('appimage');
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const state = await readCursorPatchSetState(fixture.appPath, {
|
|
55
|
+
platform: 'linux',
|
|
56
|
+
agentExecTargetRelativePath:
|
|
57
|
+
'usr/share/cursor/resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.equal(
|
|
61
|
+
state.patches.find((patch) => patch.name === 'agent-exec')?.targetPath,
|
|
62
|
+
join(fixture.resourceRoot, 'extensions/cursor-agent-exec/dist/main.js'),
|
|
63
|
+
);
|
|
64
|
+
assert.equal(
|
|
65
|
+
state.patches.find((patch) => patch.name === 'workbench')?.targetPath,
|
|
66
|
+
join(fixture.resourceRoot, 'out/vs/workbench/workbench.desktop.main.js'),
|
|
67
|
+
);
|
|
68
|
+
} finally {
|
|
69
|
+
await rm(fixture.tempDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
});
|