@cursorpool-dev/cli 0.5.6
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/cursor-pool.mjs +9 -0
- package/bin/cursor-pool.ts +169 -0
- package/node_modules/@cursor-pool/extension/dist/extension.js +2910 -0
- package/node_modules/@cursor-pool/extension/package.json +64 -0
- package/node_modules/@cursor-pool/extension/resources/cursor-pool.svg +6 -0
- package/node_modules/@cursor-pool/extension/src/api.ts +545 -0
- package/node_modules/@cursor-pool/extension/src/extension.ts +104 -0
- package/node_modules/@cursor-pool/extension/src/index.ts +1 -0
- package/node_modules/@cursor-pool/extension/src/panel.ts +569 -0
- package/node_modules/@cursor-pool/extension/src/runtime.ts +22 -0
- package/node_modules/@cursor-pool/extension/test/panel.test.ts +1785 -0
- package/node_modules/@cursor-pool/patcher/package.json +17 -0
- package/node_modules/@cursor-pool/patcher/src/alwaysLocalMarker.ts +86 -0
- package/node_modules/@cursor-pool/patcher/src/hash.ts +7 -0
- package/node_modules/@cursor-pool/patcher/src/index.ts +55 -0
- package/node_modules/@cursor-pool/patcher/src/marker.ts +159 -0
- package/node_modules/@cursor-pool/patcher/src/patchCursorAgentExec.ts +154 -0
- package/node_modules/@cursor-pool/patcher/src/patchCursorAlwaysLocal.ts +142 -0
- package/node_modules/@cursor-pool/patcher/src/patchCursorWorkbenchAuthGate.ts +140 -0
- package/node_modules/@cursor-pool/patcher/src/restoreCursorAgentExec.ts +52 -0
- package/node_modules/@cursor-pool/patcher/src/restoreCursorAlwaysLocal.ts +52 -0
- package/node_modules/@cursor-pool/patcher/src/restoreCursorWorkbenchAuthGate.ts +70 -0
- package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +243 -0
- package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +630 -0
- package/node_modules/@cursor-pool/patcher/test/patchCursorAlwaysLocal.test.ts +144 -0
- package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +770 -0
- package/node_modules/@cursor-pool/patcher/test/restoreCursorAgentExec.test.ts +139 -0
- package/node_modules/@cursor-pool/service/package.json +17 -0
- package/node_modules/@cursor-pool/service/src/canary.ts +61 -0
- package/node_modules/@cursor-pool/service/src/diagnostics.ts +385 -0
- package/node_modules/@cursor-pool/service/src/entry.ts +161 -0
- package/node_modules/@cursor-pool/service/src/health.ts +10 -0
- package/node_modules/@cursor-pool/service/src/index.ts +29 -0
- package/node_modules/@cursor-pool/service/src/metadata.ts +22 -0
- package/node_modules/@cursor-pool/service/src/platformSession.ts +1178 -0
- package/node_modules/@cursor-pool/service/src/requestCheck.ts +81 -0
- package/node_modules/@cursor-pool/service/src/requestGate.ts +100 -0
- package/node_modules/@cursor-pool/service/src/requestGateway.ts +441 -0
- package/node_modules/@cursor-pool/service/src/runtime.ts +48 -0
- package/node_modules/@cursor-pool/service/src/server.ts +939 -0
- package/node_modules/@cursor-pool/service/src/takeover.ts +111 -0
- package/node_modules/@cursor-pool/service/test/canary.test.ts +140 -0
- package/node_modules/@cursor-pool/service/test/diagnostics.test.ts +506 -0
- package/node_modules/@cursor-pool/service/test/metadata.test.ts +63 -0
- package/node_modules/@cursor-pool/service/test/platformSession.test.ts +2428 -0
- package/node_modules/@cursor-pool/service/test/requestCheck.test.ts +152 -0
- package/node_modules/@cursor-pool/service/test/requestGate.test.ts +207 -0
- package/node_modules/@cursor-pool/service/test/requestGateway.test.ts +466 -0
- package/node_modules/@cursor-pool/service/test/runtime.test.ts +47 -0
- package/node_modules/@cursor-pool/service/test/server.test.ts +2570 -0
- package/node_modules/@cursor-pool/shared/package.json +17 -0
- package/node_modules/@cursor-pool/shared/src/clientConfig.ts +49 -0
- package/node_modules/@cursor-pool/shared/src/index.ts +14 -0
- package/node_modules/@cursor-pool/shared/src/manifest.ts +36 -0
- package/node_modules/@cursor-pool/shared/src/metadata.ts +19 -0
- package/node_modules/@cursor-pool/shared/src/paths.ts +5 -0
- package/node_modules/@cursor-pool/shared/src/runtime.ts +3 -0
- package/node_modules/@cursor-pool/shared/test/index.test.ts +56 -0
- package/node_modules/@cursor-pool/shared/test/manifest.test.ts +65 -0
- package/node_modules/@cursor-pool/shared/test/metadata.test.ts +25 -0
- package/node_modules/@cursor-pool/shared/test/runtime.test.ts +8 -0
- package/package.json +28 -0
- package/src/adHocResign.ts +65 -0
- package/src/autostart.ts +240 -0
- package/src/compat.ts +282 -0
- package/src/confirm.ts +76 -0
- package/src/cursor.ts +94 -0
- package/src/diagnostics.ts +558 -0
- package/src/environment.ts +18 -0
- package/src/extensionBundle.ts +111 -0
- package/src/extensionLink.ts +168 -0
- package/src/index.ts +23 -0
- package/src/install.ts +614 -0
- package/src/installRecord.ts +105 -0
- package/src/launch.ts +182 -0
- package/src/patchSet.ts +182 -0
- package/src/platform.ts +132 -0
- package/src/repair.ts +383 -0
- package/src/restore.ts +153 -0
- package/src/serviceCommands.ts +79 -0
- package/src/serviceProcess.ts +188 -0
- package/src/status.ts +241 -0
- package/src/target.ts +37 -0
- package/src/trial.ts +133 -0
- package/src/uninstall.ts +213 -0
- package/test/autostart.test.ts +151 -0
- package/test/compat.test.ts +192 -0
- package/test/confirm.test.ts +114 -0
- package/test/cursor-pool-bin.test.ts +658 -0
- package/test/cursor.test.ts +20 -0
- package/test/diagnostics.test.ts +709 -0
- package/test/e2e-install.test.ts +773 -0
- package/test/extensionBundle.test.ts +161 -0
- package/test/extensionLink.test.ts +209 -0
- package/test/install.test.ts +862 -0
- package/test/installRecord.test.ts +107 -0
- package/test/launch.test.ts +138 -0
- package/test/platform.test.ts +226 -0
- package/test/repair.test.ts +575 -0
- package/test/restore.test.ts +211 -0
- package/test/serviceCommands.test.ts +135 -0
- package/test/serviceProcess.test.ts +280 -0
- package/test/status.test.ts +615 -0
- package/test/target.test.ts +49 -0
- package/test/trial.test.ts +146 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import {
|
|
8
|
+
bundleExtension,
|
|
9
|
+
getExtensionState,
|
|
10
|
+
removeExtensionBundle,
|
|
11
|
+
resolveExtensionInstallPath,
|
|
12
|
+
} from '../src/extensionBundle';
|
|
13
|
+
|
|
14
|
+
const cliRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
+
|
|
16
|
+
test('bundleExtension copies package manifest and dist extension entry', async () => {
|
|
17
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-bundle-'));
|
|
18
|
+
const sourceDir = join(tempDir, 'source');
|
|
19
|
+
const installPath = join(tempDir, 'installed/extensions/cursor-pool-status');
|
|
20
|
+
await mkdir(join(sourceDir, 'dist'), { recursive: true });
|
|
21
|
+
await mkdir(join(sourceDir, 'resources'), { recursive: true });
|
|
22
|
+
await writeFile(
|
|
23
|
+
join(sourceDir, 'package.json'),
|
|
24
|
+
JSON.stringify({
|
|
25
|
+
name: '@cursor-pool/extension',
|
|
26
|
+
type: 'module',
|
|
27
|
+
main: './dist/extension.js',
|
|
28
|
+
contributes: {
|
|
29
|
+
viewsContainers: {
|
|
30
|
+
activitybar: [{ id: 'cursorPoolClient', title: 'Cursor Pool 平台模式', icon: 'resources/cursor-pool.svg' }],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
'utf8',
|
|
35
|
+
);
|
|
36
|
+
await writeFile(join(sourceDir, 'dist/extension.js'), 'export function activate() {}\n', 'utf8');
|
|
37
|
+
await writeFile(join(sourceDir, 'resources/cursor-pool.svg'), '<svg />\n', 'utf8');
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await bundleExtension({ sourceDir, installPath });
|
|
41
|
+
assert.deepEqual(result, { state: 'bundled', installPath });
|
|
42
|
+
const manifest = JSON.parse(await readFile(join(installPath, 'package.json'), 'utf8'));
|
|
43
|
+
assert.equal(manifest.name, 'cursorpool');
|
|
44
|
+
assert.equal(manifest.publisher, 'cursor-pool');
|
|
45
|
+
assert.equal(manifest.displayName, 'Cursor Pool 平台模式');
|
|
46
|
+
assert.equal(manifest.type, 'module');
|
|
47
|
+
assert.equal(manifest.dependencies, undefined);
|
|
48
|
+
assert.equal(manifest.exports, undefined);
|
|
49
|
+
assert.equal(manifest.scripts, undefined);
|
|
50
|
+
assert.match(await readFile(join(installPath, 'dist/extension.js'), 'utf8'), /activate/);
|
|
51
|
+
assert.equal(await readFile(join(installPath, 'resources/cursor-pool.svg'), 'utf8'), '<svg />\n');
|
|
52
|
+
assert.equal(await getExtensionState(installPath), 'bundled');
|
|
53
|
+
|
|
54
|
+
await removeExtensionBundle(installPath);
|
|
55
|
+
assert.equal(await getExtensionState(installPath), 'missing');
|
|
56
|
+
} finally {
|
|
57
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('bundleExtension default source works from package-local cwd', async () => {
|
|
62
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-default-source-'));
|
|
63
|
+
const installPath = join(tempDir, 'installed/extensions/cursor-pool-status');
|
|
64
|
+
const previousCwd = process.cwd();
|
|
65
|
+
process.chdir(cliRoot);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = await bundleExtension({ installPath });
|
|
69
|
+
assert.deepEqual(result, { state: 'bundled', installPath });
|
|
70
|
+
const manifest = JSON.parse(await readFile(join(installPath, 'package.json'), 'utf8'));
|
|
71
|
+
assert.equal(manifest.name, 'cursorpool');
|
|
72
|
+
assert.equal(manifest.publisher, 'cursor-pool');
|
|
73
|
+
assert.equal(manifest.displayName, 'Cursor Pool 平台模式');
|
|
74
|
+
assert.equal(manifest.type, 'module');
|
|
75
|
+
assert.match(await readFile(join(installPath, 'dist/extension.js'), 'utf8'), /activate/);
|
|
76
|
+
await access(join(installPath, 'resources/cursor-pool.svg'));
|
|
77
|
+
} finally {
|
|
78
|
+
process.chdir(previousCwd);
|
|
79
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('bundleExtension reports missing when dist entry is absent', async () => {
|
|
84
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-missing-'));
|
|
85
|
+
const sourceDir = join(tempDir, 'source');
|
|
86
|
+
const installPath = join(tempDir, 'installed/extensions/cursor-pool-status');
|
|
87
|
+
await mkdir(sourceDir, { recursive: true });
|
|
88
|
+
await writeFile(join(sourceDir, 'package.json'), JSON.stringify({ name: 'x' }), 'utf8');
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const result = await bundleExtension({ sourceDir, installPath });
|
|
92
|
+
assert.deepEqual(result, { state: 'missing', installPath });
|
|
93
|
+
assert.equal(await getExtensionState(installPath), 'missing');
|
|
94
|
+
} finally {
|
|
95
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('bundleExtension rejects unsafe install paths without removing them', async () => {
|
|
100
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-unsafe-bundle-'));
|
|
101
|
+
const sourceDir = join(tempDir, 'source');
|
|
102
|
+
const unsafeInstallPath = join(tempDir, 'unsafe-install-root');
|
|
103
|
+
await mkdir(join(sourceDir, 'dist'), { recursive: true });
|
|
104
|
+
await mkdir(unsafeInstallPath, { recursive: true });
|
|
105
|
+
await writeFile(join(sourceDir, 'package.json'), JSON.stringify({ name: 'x' }), 'utf8');
|
|
106
|
+
await writeFile(join(sourceDir, 'dist/extension.js'), 'export function activate() {}\n', 'utf8');
|
|
107
|
+
await writeFile(join(unsafeInstallPath, 'sentinel.txt'), 'keep me\n', 'utf8');
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await assert.rejects(
|
|
111
|
+
bundleExtension({ sourceDir, installPath: unsafeInstallPath }),
|
|
112
|
+
/Unsafe extension install path/,
|
|
113
|
+
);
|
|
114
|
+
assert.equal(await readFile(join(unsafeInstallPath, 'sentinel.txt'), 'utf8'), 'keep me\n');
|
|
115
|
+
} finally {
|
|
116
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('removeExtensionBundle rejects unsafe home path without removing it', async () => {
|
|
121
|
+
const originalHome = process.env.HOME;
|
|
122
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-unsafe-home-'));
|
|
123
|
+
const fakeHome = join(tempDir, 'home');
|
|
124
|
+
const unsafeBundleRoot = join(fakeHome, '.cursor-pool');
|
|
125
|
+
await mkdir(unsafeBundleRoot, { recursive: true });
|
|
126
|
+
await writeFile(join(unsafeBundleRoot, 'sentinel.txt'), 'keep me\n', 'utf8');
|
|
127
|
+
process.env.HOME = fakeHome;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await assert.rejects(removeExtensionBundle('~/.cursor-pool'), /Unsafe extension install path/);
|
|
131
|
+
assert.equal(await readFile(join(unsafeBundleRoot, 'sentinel.txt'), 'utf8'), 'keep me\n');
|
|
132
|
+
} finally {
|
|
133
|
+
if (originalHome === undefined) {
|
|
134
|
+
delete process.env.HOME;
|
|
135
|
+
} else {
|
|
136
|
+
process.env.HOME = originalHome;
|
|
137
|
+
}
|
|
138
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('resolveExtensionInstallPath expands home-relative install paths', async () => {
|
|
143
|
+
const originalHome = process.env.HOME;
|
|
144
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-home-'));
|
|
145
|
+
const fakeHome = join(tempDir, 'home');
|
|
146
|
+
process.env.HOME = fakeHome;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
assert.equal(
|
|
150
|
+
resolveExtensionInstallPath('~/extensions/cursor-pool-status'),
|
|
151
|
+
join(fakeHome, 'extensions/cursor-pool-status'),
|
|
152
|
+
);
|
|
153
|
+
} finally {
|
|
154
|
+
if (originalHome === undefined) {
|
|
155
|
+
delete process.env.HOME;
|
|
156
|
+
} else {
|
|
157
|
+
process.env.HOME = originalHome;
|
|
158
|
+
}
|
|
159
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, 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 {
|
|
7
|
+
getLinkedExtensionState,
|
|
8
|
+
linkExtensionBundle,
|
|
9
|
+
linkedExtensionPathForDir,
|
|
10
|
+
removeLinkedExtensionBundle,
|
|
11
|
+
restoreLinkedExtensionSnapshot,
|
|
12
|
+
snapshotLinkedExtensionBundle,
|
|
13
|
+
} from '../src/extensionLink';
|
|
14
|
+
|
|
15
|
+
async function createSourceBundle(tempDir: string) {
|
|
16
|
+
const sourceBundlePath = join(tempDir, 'source/extensions/cursor-pool-status');
|
|
17
|
+
await mkdir(join(sourceBundlePath, 'dist'), { recursive: true });
|
|
18
|
+
await writeFile(
|
|
19
|
+
join(sourceBundlePath, 'package.json'),
|
|
20
|
+
JSON.stringify({ name: 'cursorpool', publisher: 'cursor-pool', version: '0.0.0' }),
|
|
21
|
+
'utf8',
|
|
22
|
+
);
|
|
23
|
+
await writeFile(join(sourceBundlePath, 'dist/extension.js'), 'export function activate() {}\n', 'utf8');
|
|
24
|
+
return sourceBundlePath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('linkedExtensionPathForDir builds the deterministic Cursor extension path', async () => {
|
|
28
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-path-'));
|
|
29
|
+
try {
|
|
30
|
+
assert.equal(
|
|
31
|
+
linkedExtensionPathForDir(join(tempDir, 'Cursor-Pool-Trial-Extensions')),
|
|
32
|
+
join(tempDir, 'Cursor-Pool-Trial-Extensions/cursor-pool.extension-0.0.0'),
|
|
33
|
+
);
|
|
34
|
+
} finally {
|
|
35
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('linkExtensionBundle copies source bundle into Cursor extensions directory', async () => {
|
|
40
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-'));
|
|
41
|
+
const sourceBundlePath = await createSourceBundle(tempDir);
|
|
42
|
+
const cursorExtensionsDir = join(tempDir, 'Extensions');
|
|
43
|
+
const linkedPath = linkedExtensionPathForDir(cursorExtensionsDir);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const result = await linkExtensionBundle({ sourceBundlePath, cursorExtensionsDir });
|
|
47
|
+
|
|
48
|
+
assert.deepEqual(result, { state: 'linked', linkedPath });
|
|
49
|
+
assert.equal(await getLinkedExtensionState(linkedPath), 'linked');
|
|
50
|
+
assert.match(await readFile(join(linkedPath, 'package.json'), 'utf8'), /cursorpool/);
|
|
51
|
+
assert.match(await readFile(join(linkedPath, 'dist/extension.js'), 'utf8'), /activate/);
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('linkExtensionBundle refreshes Cursor extensions index with runtime extension id', async () => {
|
|
58
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-index-'));
|
|
59
|
+
const sourceBundlePath = await createSourceBundle(tempDir);
|
|
60
|
+
const cursorExtensionsDir = join(tempDir, 'Extensions');
|
|
61
|
+
await mkdir(cursorExtensionsDir, { recursive: true });
|
|
62
|
+
await writeFile(
|
|
63
|
+
join(cursorExtensionsDir, 'extensions.json'),
|
|
64
|
+
JSON.stringify([
|
|
65
|
+
{
|
|
66
|
+
identifier: { id: 'cursor-pool.@cursor-pool/extension' },
|
|
67
|
+
relativeLocation: 'cursor-pool.extension-0.0.0',
|
|
68
|
+
version: '0.0.0',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
identifier: { id: 'keg1255.cursorpool' },
|
|
72
|
+
relativeLocation: 'keg1255.cursorpool-1.0.52',
|
|
73
|
+
version: '1.0.52',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
identifier: { id: 'cursor-pool.cursorpool' },
|
|
77
|
+
relativeLocation: 'cursor-pool.cursorpool-0.0.0',
|
|
78
|
+
version: '0.0.0',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
identifier: { id: 'publisher.other' },
|
|
82
|
+
relativeLocation: 'publisher.other-1.0.0',
|
|
83
|
+
version: '1.0.0',
|
|
84
|
+
},
|
|
85
|
+
]),
|
|
86
|
+
'utf8',
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const result = await linkExtensionBundle({ sourceBundlePath, cursorExtensionsDir });
|
|
91
|
+
const index = JSON.parse(await readFile(join(cursorExtensionsDir, 'extensions.json'), 'utf8'));
|
|
92
|
+
|
|
93
|
+
assert.equal(
|
|
94
|
+
index.some((entry: { identifier?: { id?: string } }) =>
|
|
95
|
+
entry.identifier?.id === 'cursor-pool.@cursor-pool/extension',
|
|
96
|
+
),
|
|
97
|
+
false,
|
|
98
|
+
);
|
|
99
|
+
assert.equal(
|
|
100
|
+
index.some((entry: { relativeLocation?: string }) =>
|
|
101
|
+
entry.relativeLocation === 'keg1255.cursorpool-1.0.52',
|
|
102
|
+
),
|
|
103
|
+
false,
|
|
104
|
+
);
|
|
105
|
+
assert.equal(
|
|
106
|
+
index.some((entry: { identifier?: { id?: string } }) =>
|
|
107
|
+
entry.identifier?.id === 'publisher.other',
|
|
108
|
+
),
|
|
109
|
+
true,
|
|
110
|
+
);
|
|
111
|
+
assert.deepEqual(index.at(-1), {
|
|
112
|
+
identifier: { id: 'cursor-pool.cursorpool' },
|
|
113
|
+
location: {
|
|
114
|
+
$mid: 1,
|
|
115
|
+
path: result.linkedPath,
|
|
116
|
+
scheme: 'file',
|
|
117
|
+
},
|
|
118
|
+
relativeLocation: 'cursor-pool.extension-0.0.0',
|
|
119
|
+
version: '0.0.0',
|
|
120
|
+
});
|
|
121
|
+
} finally {
|
|
122
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('linkExtensionBundle reports missing when source bundle is absent', async () => {
|
|
127
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-missing-'));
|
|
128
|
+
const cursorExtensionsDir = join(tempDir, 'Extensions');
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const result = await linkExtensionBundle({
|
|
132
|
+
sourceBundlePath: join(tempDir, 'source/extensions/cursor-pool-status'),
|
|
133
|
+
cursorExtensionsDir,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
assert.deepEqual(result, {
|
|
137
|
+
state: 'missing',
|
|
138
|
+
linkedPath: linkedExtensionPathForDir(cursorExtensionsDir),
|
|
139
|
+
});
|
|
140
|
+
assert.equal(await getLinkedExtensionState(result.linkedPath), 'missing');
|
|
141
|
+
} finally {
|
|
142
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('removeLinkedExtensionBundle rejects unsafe paths without removing bytes', async () => {
|
|
147
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-unsafe-'));
|
|
148
|
+
const unsafePath = join(tempDir, 'not-the-linked-extension');
|
|
149
|
+
await mkdir(unsafePath, { recursive: true });
|
|
150
|
+
await writeFile(join(unsafePath, 'sentinel.txt'), 'keep me\n', 'utf8');
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await assert.rejects(removeLinkedExtensionBundle(unsafePath), /Unsafe linked extension path/);
|
|
154
|
+
assert.equal(await readFile(join(unsafePath, 'sentinel.txt'), 'utf8'), 'keep me\n');
|
|
155
|
+
} finally {
|
|
156
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('removeLinkedExtensionBundle rejects paths where extensions is not an exact segment', async () => {
|
|
161
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-unsafe-segment-'));
|
|
162
|
+
const unsafePath = join(tempDir, 'not-extensions/cursor-pool.extension-0.0.0');
|
|
163
|
+
await mkdir(unsafePath, { recursive: true });
|
|
164
|
+
await writeFile(join(unsafePath, 'sentinel.txt'), 'keep me\n', 'utf8');
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await assert.rejects(removeLinkedExtensionBundle(unsafePath), /Unsafe linked extension path/);
|
|
168
|
+
assert.equal(await readFile(join(unsafePath, 'sentinel.txt'), 'utf8'), 'keep me\n');
|
|
169
|
+
} finally {
|
|
170
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('linked extension snapshot restores pre-existing linked bytes', async () => {
|
|
175
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-snapshot-'));
|
|
176
|
+
const cursorExtensionsDir = join(tempDir, 'Extensions');
|
|
177
|
+
const linkedPath = linkedExtensionPathForDir(cursorExtensionsDir);
|
|
178
|
+
await mkdir(join(linkedPath, 'dist'), { recursive: true });
|
|
179
|
+
await writeFile(join(linkedPath, 'package.json'), JSON.stringify({ name: 'sentinel' }), 'utf8');
|
|
180
|
+
await writeFile(join(linkedPath, 'dist/extension.js'), 'sentinel\n', 'utf8');
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const snapshot = await snapshotLinkedExtensionBundle(linkedPath);
|
|
184
|
+
await rm(linkedPath, { recursive: true, force: true });
|
|
185
|
+
await mkdir(join(linkedPath, 'dist'), { recursive: true });
|
|
186
|
+
await writeFile(join(linkedPath, 'package.json'), JSON.stringify({ name: 'new' }), 'utf8');
|
|
187
|
+
await writeFile(join(linkedPath, 'dist/extension.js'), 'new\n', 'utf8');
|
|
188
|
+
|
|
189
|
+
await restoreLinkedExtensionSnapshot(linkedPath, snapshot);
|
|
190
|
+
|
|
191
|
+
assert.equal(await readFile(join(linkedPath, 'dist/extension.js'), 'utf8'), 'sentinel\n');
|
|
192
|
+
await rm(snapshot?.root ?? '', { recursive: true, force: true });
|
|
193
|
+
} finally {
|
|
194
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('linked extension snapshot returns null for missing path and null restore is a no-op', async () => {
|
|
199
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-snapshot-missing-'));
|
|
200
|
+
const linkedPath = linkedExtensionPathForDir(join(tempDir, 'Extensions'));
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
assert.equal(await snapshotLinkedExtensionBundle(linkedPath), null);
|
|
204
|
+
await restoreLinkedExtensionSnapshot(linkedPath, null);
|
|
205
|
+
assert.equal(await getLinkedExtensionState(linkedPath), 'missing');
|
|
206
|
+
} finally {
|
|
207
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
208
|
+
}
|
|
209
|
+
});
|