@lvce-editor/shared-process 0.0.2
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/LICENSE +21 -0
- package/README.md +41 -0
- package/bin/sharedProcess.js +3 -0
- package/index.js +6 -0
- package/package.json +96 -0
- package/src/parts/Assert/Assert.js +57 -0
- package/src/parts/Callback/Callback.js +48 -0
- package/src/parts/ChildProcess/ChildProcess.js +98 -0
- package/src/parts/ClipBoard/ClipBoard.ipc.js +7 -0
- package/src/parts/ClipBoard/ClipBoard.js +23 -0
- package/src/parts/ClipBoard/ClipBoardGnome.js +78 -0
- package/src/parts/ClipBoard/ClipBoardNoop.js +14 -0
- package/src/parts/ClipBoard/ClipBoardWindows.js +22 -0
- package/src/parts/Command/Command.js +264 -0
- package/src/parts/Configuration/Configuration.ipc.js +7 -0
- package/src/parts/Configuration/Configuration.js +39 -0
- package/src/parts/Credentials/Credentials.ipc.js +10 -0
- package/src/parts/Credentials/Credentials.js +69 -0
- package/src/parts/Debug/Debug.js +3 -0
- package/src/parts/Developer/Developer.ipc.js +20 -0
- package/src/parts/Developer/Developer.js +132 -0
- package/src/parts/Download/Download.js +33 -0
- package/src/parts/Electron/Electron.ipc.js +19 -0
- package/src/parts/Electron/Electron.js +99 -0
- package/src/parts/Env/Env.js +3 -0
- package/src/parts/Error/Error.js +30 -0
- package/src/parts/ErrorHandling/ErrorHandling.js +40 -0
- package/src/parts/Exec/Exec.js +16 -0
- package/src/parts/Extension/Extension.js +7 -0
- package/src/parts/ExtensionHost/ExtensionHost.ipc.js +80 -0
- package/src/parts/ExtensionHost/ExtensionHost.js +163 -0
- package/src/parts/ExtensionHost/ExtensionHostBraceCompletion.js +14 -0
- package/src/parts/ExtensionHost/ExtensionHostClosingTag.js +14 -0
- package/src/parts/ExtensionHost/ExtensionHostColorTheme.js +60 -0
- package/src/parts/ExtensionHost/ExtensionHostCommand.js +19 -0
- package/src/parts/ExtensionHost/ExtensionHostCompletion.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostDefinition.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostDiagnostic.js +7 -0
- package/src/parts/ExtensionHost/ExtensionHostFileSystem.js +23 -0
- package/src/parts/ExtensionHost/ExtensionHostFormatting.js +4 -0
- package/src/parts/ExtensionHost/ExtensionHostHover.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostIconTheme.js +35 -0
- package/src/parts/ExtensionHost/ExtensionHostImplementation.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostKeyBindings.js +23 -0
- package/src/parts/ExtensionHost/ExtensionHostLanguages.js +84 -0
- package/src/parts/ExtensionHost/ExtensionHostManagement.js +26 -0
- package/src/parts/ExtensionHost/ExtensionHostOutput.js +3 -0
- package/src/parts/ExtensionHost/ExtensionHostQuickPick.js +6 -0
- package/src/parts/ExtensionHost/ExtensionHostReference.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostRename.js +27 -0
- package/src/parts/ExtensionHost/ExtensionHostSemanticTokens.js +10 -0
- package/src/parts/ExtensionHost/ExtensionHostSourceControl.js +15 -0
- package/src/parts/ExtensionHost/ExtensionHostStats.js +3 -0
- package/src/parts/ExtensionHost/ExtensionHostStatusBar.js +7 -0
- package/src/parts/ExtensionHost/ExtensionHostTabCompletion.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostTextDocument.js +35 -0
- package/src/parts/ExtensionHost/ExtensionHostTypeDefinition.js +12 -0
- package/src/parts/ExtensionHost/ExtensionHostWorkspace.js +3 -0
- package/src/parts/ExtensionManagement/ExtensionManagement.ipc.js +11 -0
- package/src/parts/ExtensionManagement/ExtensionManagement.js +319 -0
- package/src/parts/FileSystem/FileSystem.ipc.js +32 -0
- package/src/parts/FileSystem/FileSystem.js +228 -0
- package/src/parts/Git/Git.js +58 -0
- package/src/parts/Json/Json.js +67 -0
- package/src/parts/JsonFile/JsonFile.js +14 -0
- package/src/parts/JsonRpc/JsonRpc.js +22 -0
- package/src/parts/Native/Native.ipc.js +6 -0
- package/src/parts/Native/Native.js +14 -0
- package/src/parts/OutputChannel/OutputChannel.ipc.js +51 -0
- package/src/parts/OutputChannel/OutputChannel.js +37 -0
- package/src/parts/ParentIpc/ParentIpc.js +203 -0
- package/src/parts/Path/Path.js +17 -0
- package/src/parts/Platform/Platform.ipc.js +18 -0
- package/src/parts/Platform/Platform.js +165 -0
- package/src/parts/Preferences/Preferences.ipc.js +6 -0
- package/src/parts/Preferences/Preferences.js +75 -0
- package/src/parts/PrettyError/PrettyError.js +86 -0
- package/src/parts/Process/Process.ipc.js +7 -0
- package/src/parts/Process/Process.js +7 -0
- package/src/parts/Queue/Queue.js +35 -0
- package/src/parts/RequiresSocket/RequiresSocket.js +11 -0
- package/src/parts/RgPath/RgPath.js +1 -0
- package/src/parts/Root/Root.js +6 -0
- package/src/parts/Search/Search.ipc.js +6 -0
- package/src/parts/Search/Search.js +101 -0
- package/src/parts/SearchFile/SearchFile.ipc.js +6 -0
- package/src/parts/SearchFile/SearchFile.js +51 -0
- package/src/parts/Socket/Socket.js +132 -0
- package/src/parts/Stats/Stats.js +45 -0
- package/src/parts/Terminal/Terminal.ipc.js +45 -0
- package/src/parts/Terminal/Terminal.js +161 -0
- package/src/parts/TextDocument/TextDocument.ipc.js +6 -0
- package/src/parts/TextDocument/TextDocument.js +37 -0
- package/src/parts/Trace/Trace.js +15 -0
- package/src/parts/Trash/Trash.js +11 -0
- package/src/parts/WebSocketServer/WebSocketServer.ipc.js +6 -0
- package/src/parts/WebSocketServer/WebSocketServer.js +43 -0
- package/src/parts/Workspace/Workspace.ipc.js +7 -0
- package/src/parts/Workspace/Workspace.js +107 -0
- package/src/sharedProcessMain.js +61 -0
- package/test/Callback.test.js +42 -0
- package/test/ClipBoard.test.js +105 -0
- package/test/Credentials.test.js +147 -0
- package/test/Developer.test.js +34 -0
- package/test/Electron.test.js +93 -0
- package/test/Error.test.js +16 -0
- package/test/Exec.test.js +25 -0
- package/test/ExtensionHost.test.js +127 -0
- package/test/ExtensionHostColorTheme.test.js +141 -0
- package/test/ExtensionHostCommand.test.js +27 -0
- package/test/ExtensionHostCompletion.test.js +45 -0
- package/test/ExtensionHostDefinition.test.js +35 -0
- package/test/ExtensionHostDiagnostic.test.js +23 -0
- package/test/ExtensionHostFileSystem.test.js +91 -0
- package/test/ExtensionHostFormatting.test.js +27 -0
- package/test/ExtensionHostIconTheme.test.js +73 -0
- package/test/ExtensionHostLanguages.test.js +212 -0
- package/test/ExtensionHostOutput.test.js +23 -0
- package/test/ExtensionHostRename.test.js +59 -0
- package/test/ExtensionHostSemanticTokens.test.js +26 -0
- package/test/ExtensionHostTabCompletion.test.js +35 -0
- package/test/ExtensionHostTextDocument.test.js +61 -0
- package/test/ExtensionHostTypeDefinition.test.js +35 -0
- package/test/ExtensionHostWorkspace.test.js +21 -0
- package/test/ExtensionManagement.test.js +408 -0
- package/test/FileSystem.test.js +294 -0
- package/test/Json.test.js +25 -0
- package/test/Native.test.js +31 -0
- package/test/OutputChannel.test.js +68 -0
- package/test/Path.test.js +17 -0
- package/test/Platform.test.js +61 -0
- package/test/Preferences.test.js +140 -0
- package/test/Process.test.js +9 -0
- package/test/RgPath.test.js +5 -0
- package/test/Search.test.js +45 -0
- package/test/SearchFile.test.js +80 -0
- package/test/Terminal.test.js +53 -0
- package/test/Trash.test.js +34 -0
- package/test/WebSocketServer.test.js +98 -0
- package/test/fixture-search-1/index.html +10 -0
- package/test/fixture-search-file-1/fileA +0 -0
- package/test/fixture-search-file-1/fileB +0 -0
- package/test/fixture-search-file-1/nested/fileC +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as ExtensionHostRename from '../src/parts/ExtensionHost/ExtensionHostRename.js'
|
|
2
|
+
|
|
3
|
+
test('executePrepareRename', async () => {
|
|
4
|
+
const fakeExtensionHost = {
|
|
5
|
+
invoke() {
|
|
6
|
+
return {
|
|
7
|
+
canRename: true,
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
expect(
|
|
12
|
+
await ExtensionHostRename.executePrepareRename(fakeExtensionHost, 0, 0)
|
|
13
|
+
).toEqual({
|
|
14
|
+
canRename: true,
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('executePrepareRename - error', async () => {
|
|
19
|
+
const fakeExtensionHost = {
|
|
20
|
+
invoke() {
|
|
21
|
+
throw new TypeError('x is not a function')
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
await expect(
|
|
25
|
+
ExtensionHostRename.executePrepareRename(fakeExtensionHost, 0, 0)
|
|
26
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('executeRename', async () => {
|
|
30
|
+
const fakeExtensionHost = {
|
|
31
|
+
invoke() {
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
file: '/test/file.txt',
|
|
35
|
+
edits: [],
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
expect(
|
|
41
|
+
await ExtensionHostRename.executeRename(fakeExtensionHost, 0, 0, '')
|
|
42
|
+
).toEqual([
|
|
43
|
+
{
|
|
44
|
+
file: '/test/file.txt',
|
|
45
|
+
edits: [],
|
|
46
|
+
},
|
|
47
|
+
])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('executeRename - error', async () => {
|
|
51
|
+
const fakeExtensionHost = {
|
|
52
|
+
invoke() {
|
|
53
|
+
throw new TypeError('x is not a function')
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
await expect(
|
|
57
|
+
ExtensionHostRename.executeRename(fakeExtensionHost, 0, 0, '')
|
|
58
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
59
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as ExtensionHostSemanticTokens from '../src/parts/ExtensionHost/ExtensionHostSemanticTokens.js'
|
|
2
|
+
|
|
3
|
+
test('executeSemanticTokenProvider', async () => {
|
|
4
|
+
const fakeExtensionHost = {
|
|
5
|
+
invoke() {
|
|
6
|
+
return [1, 2, 3, 4, 5]
|
|
7
|
+
},
|
|
8
|
+
}
|
|
9
|
+
expect(
|
|
10
|
+
await ExtensionHostSemanticTokens.executeSemanticTokenProvider(
|
|
11
|
+
fakeExtensionHost,
|
|
12
|
+
0
|
|
13
|
+
)
|
|
14
|
+
).toEqual([1, 2, 3, 4, 5])
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('executeCompletionProvider - error', async () => {
|
|
18
|
+
const fakeExtensionHost = {
|
|
19
|
+
invoke() {
|
|
20
|
+
throw new TypeError('x is not a function')
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
await expect(
|
|
24
|
+
ExtensionHostSemanticTokens.executeSemanticTokenProvider(fakeExtensionHost)
|
|
25
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
26
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as ExtensionHostTabCompletion from '../src/parts/ExtensionHost/ExtensionHostTabCompletion.js'
|
|
2
|
+
|
|
3
|
+
test('executeTabCompletionProvider', async () => {
|
|
4
|
+
const fakeExtensionHost = {
|
|
5
|
+
invoke() {
|
|
6
|
+
return {
|
|
7
|
+
inserted: '<div></div>',
|
|
8
|
+
deleted: 0,
|
|
9
|
+
type: /* Snippet */ 2,
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
expect(
|
|
14
|
+
await ExtensionHostTabCompletion.executeTabCompletionProvider(
|
|
15
|
+
fakeExtensionHost,
|
|
16
|
+
0,
|
|
17
|
+
0
|
|
18
|
+
)
|
|
19
|
+
).toEqual({
|
|
20
|
+
inserted: '<div></div>',
|
|
21
|
+
deleted: 0,
|
|
22
|
+
type: /* Snippet */ 2,
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('executeTabCompletionProvider - error', async () => {
|
|
27
|
+
const fakeExtensionHost = {
|
|
28
|
+
invoke() {
|
|
29
|
+
throw new TypeError('x is not a function')
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
await expect(
|
|
33
|
+
ExtensionHostTabCompletion.executeTabCompletionProvider(fakeExtensionHost)
|
|
34
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
35
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as ExtensionHostTextDocument from '../src/parts/ExtensionHost/ExtensionHostTextDocument.js'
|
|
2
|
+
|
|
3
|
+
test('textDocumentSyncInitial', async () => {
|
|
4
|
+
const fakeExtensionHost = {
|
|
5
|
+
invoke() {},
|
|
6
|
+
}
|
|
7
|
+
expect(
|
|
8
|
+
await ExtensionHostTextDocument.textDocumentSyncInitial(
|
|
9
|
+
fakeExtensionHost,
|
|
10
|
+
'/test/file.txt',
|
|
11
|
+
0,
|
|
12
|
+
'',
|
|
13
|
+
''
|
|
14
|
+
)
|
|
15
|
+
).toEqual(undefined)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('textDocumentSyncInitial - error', async () => {
|
|
19
|
+
const fakeExtensionHost = {
|
|
20
|
+
async invoke() {
|
|
21
|
+
throw new TypeError('x is not a function')
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
await expect(
|
|
25
|
+
ExtensionHostTextDocument.textDocumentSyncInitial(
|
|
26
|
+
fakeExtensionHost,
|
|
27
|
+
'/test/file.txt',
|
|
28
|
+
0,
|
|
29
|
+
'',
|
|
30
|
+
''
|
|
31
|
+
)
|
|
32
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('textDocumentSyncIncremental', async () => {
|
|
36
|
+
const fakeExtensionHost = {
|
|
37
|
+
invoke() {},
|
|
38
|
+
}
|
|
39
|
+
expect(
|
|
40
|
+
await ExtensionHostTextDocument.textDocumentSyncIncremental(
|
|
41
|
+
fakeExtensionHost,
|
|
42
|
+
0,
|
|
43
|
+
[]
|
|
44
|
+
)
|
|
45
|
+
).toEqual(undefined)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('textDocumentSyncInitial - error', async () => {
|
|
49
|
+
const fakeExtensionHost = {
|
|
50
|
+
async invoke() {
|
|
51
|
+
throw new TypeError('x is not a function')
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
await expect(
|
|
55
|
+
ExtensionHostTextDocument.textDocumentSyncIncremental(
|
|
56
|
+
fakeExtensionHost,
|
|
57
|
+
0,
|
|
58
|
+
[]
|
|
59
|
+
)
|
|
60
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
61
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as ExtensionHostTypeDefinition from '../src/parts/ExtensionHost/ExtensionHostTypeDefinition.js'
|
|
2
|
+
|
|
3
|
+
test('executeTypeDefinitionProvider', async () => {
|
|
4
|
+
const fakeExtensionHost = {
|
|
5
|
+
invoke() {
|
|
6
|
+
return {
|
|
7
|
+
uri: '/test/file.txt',
|
|
8
|
+
startOffset: 0,
|
|
9
|
+
endOffset: 0,
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
expect(
|
|
14
|
+
await ExtensionHostTypeDefinition.executeTypeDefinitionProvider(
|
|
15
|
+
fakeExtensionHost,
|
|
16
|
+
0,
|
|
17
|
+
0
|
|
18
|
+
)
|
|
19
|
+
).toEqual({
|
|
20
|
+
uri: '/test/file.txt',
|
|
21
|
+
startOffset: 0,
|
|
22
|
+
endOffset: 0,
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('executeTypeDefinitionProvider - error', async () => {
|
|
27
|
+
const fakeExtensionHost = {
|
|
28
|
+
invoke() {
|
|
29
|
+
throw new TypeError('x is not a function')
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
await expect(
|
|
33
|
+
ExtensionHostTypeDefinition.executeTypeDefinitionProvider(fakeExtensionHost)
|
|
34
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
35
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as ExtensionHostWorkspace from '../src/parts/ExtensionHost/ExtensionHostWorkspace.js'
|
|
2
|
+
|
|
3
|
+
test('setWorkspacePath', async () => {
|
|
4
|
+
const fakeExtensionHost = {
|
|
5
|
+
invoke() {},
|
|
6
|
+
}
|
|
7
|
+
expect(
|
|
8
|
+
await ExtensionHostWorkspace.setWorkspacePath(fakeExtensionHost, '/test')
|
|
9
|
+
).toEqual(undefined)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('setWorkspacePath - error', async () => {
|
|
13
|
+
const fakeExtensionHost = {
|
|
14
|
+
async invoke() {
|
|
15
|
+
throw new TypeError('x is not a function')
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
await expect(
|
|
19
|
+
ExtensionHostWorkspace.setWorkspacePath(fakeExtensionHost, '/test')
|
|
20
|
+
).rejects.toThrowError(new TypeError('x is not a function'))
|
|
21
|
+
})
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream } from 'node:fs'
|
|
2
|
+
import {
|
|
3
|
+
access,
|
|
4
|
+
mkdir,
|
|
5
|
+
mkdtemp,
|
|
6
|
+
readdir,
|
|
7
|
+
readFile,
|
|
8
|
+
rm,
|
|
9
|
+
writeFile,
|
|
10
|
+
} from 'node:fs/promises'
|
|
11
|
+
import http from 'node:http'
|
|
12
|
+
import { tmpdir } from 'node:os'
|
|
13
|
+
import { dirname, join } from 'node:path'
|
|
14
|
+
import { pipeline } from 'node:stream/promises'
|
|
15
|
+
import { constants, createBrotliCompress } from 'node:zlib'
|
|
16
|
+
import getPort from 'get-port'
|
|
17
|
+
import { jest } from '@jest/globals'
|
|
18
|
+
import tar from 'tar-fs'
|
|
19
|
+
import VError from 'verror'
|
|
20
|
+
import * as ExtensionManagement from '../src/parts/ExtensionManagement/ExtensionManagement.js'
|
|
21
|
+
import * as Platform from '../src/parts/Platform/Platform.js'
|
|
22
|
+
|
|
23
|
+
const getTmpDir = () => {
|
|
24
|
+
return mkdtemp(join(tmpdir(), 'foo-'))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const exists = async (path) => {
|
|
28
|
+
try {
|
|
29
|
+
await access(path)
|
|
30
|
+
return true
|
|
31
|
+
} catch {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} inDir
|
|
38
|
+
* @param {string} outFile
|
|
39
|
+
*/
|
|
40
|
+
export const compress = async (inDir, outFile) => {
|
|
41
|
+
await mkdir(dirname(outFile), { recursive: true })
|
|
42
|
+
await pipeline(
|
|
43
|
+
tar.pack(inDir),
|
|
44
|
+
createBrotliCompress({
|
|
45
|
+
params: {
|
|
46
|
+
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MIN_QUALITY,
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
createWriteStream(outFile)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const createExtensionTarBr = async (files) => {
|
|
54
|
+
const folder = '/tmp/extension-test/test-author.test-extension'
|
|
55
|
+
await rm(folder, { force: true, recursive: true })
|
|
56
|
+
await mkdir(folder, { recursive: true })
|
|
57
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
58
|
+
await writeFile(`${folder}/${filePath}`, content)
|
|
59
|
+
}
|
|
60
|
+
await compress(folder, `${folder}.tar.br`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let server
|
|
64
|
+
let handler
|
|
65
|
+
|
|
66
|
+
beforeAll(async () => {
|
|
67
|
+
const port = await getPort()
|
|
68
|
+
server = http.createServer((request, response) => {
|
|
69
|
+
handler(request, response)
|
|
70
|
+
})
|
|
71
|
+
await new Promise((resolve) => {
|
|
72
|
+
server.listen(port, () => {
|
|
73
|
+
resolve(undefined)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
process.env.LVCE_MARKETPLACE_URL = `http://localhost:${port}`
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
afterAll(() => {
|
|
80
|
+
server.close()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
jest.restoreAllMocks()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// TODO this test is flaky sometimes: Jest has detected the following 1 open handle potentially keeping Jest from exiting: ● JSSTREAM
|
|
88
|
+
test.skip('install', async () => {
|
|
89
|
+
await createExtensionTarBr({
|
|
90
|
+
'extension.json': `{
|
|
91
|
+
"id": "test-author.test-extension",
|
|
92
|
+
"name": "test-extension",
|
|
93
|
+
"publisher": "test-author",
|
|
94
|
+
"version": "0.0.1",
|
|
95
|
+
"main": "main.js"
|
|
96
|
+
}
|
|
97
|
+
`,
|
|
98
|
+
'main.js': 'export const activate = () => { console.info("hello world") }',
|
|
99
|
+
'package.json': '{ "type" : "module" }',
|
|
100
|
+
})
|
|
101
|
+
handler = async (request, response) => {
|
|
102
|
+
switch (request.url) {
|
|
103
|
+
case '/download/test-author.test-extension':
|
|
104
|
+
response.statusCode = 200
|
|
105
|
+
await pipeline(
|
|
106
|
+
createReadStream(
|
|
107
|
+
'/tmp/extension-test/test-author.test-extension.tar.br'
|
|
108
|
+
),
|
|
109
|
+
response
|
|
110
|
+
)
|
|
111
|
+
break
|
|
112
|
+
default:
|
|
113
|
+
response.statusCode = 404
|
|
114
|
+
response.end()
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const tmpDir = await getTmpDir()
|
|
119
|
+
Platform.state.getExtensionsPath = () => tmpDir
|
|
120
|
+
await ExtensionManagement.install('test-author.test-extension')
|
|
121
|
+
expect(
|
|
122
|
+
await readFile(
|
|
123
|
+
join(tmpDir, 'test-author.test-extension/extension.json'),
|
|
124
|
+
'utf-8'
|
|
125
|
+
)
|
|
126
|
+
).toBe(`{
|
|
127
|
+
"id": "test-author.test-extension",
|
|
128
|
+
"name": "test-extension",
|
|
129
|
+
"publisher": "test-author",
|
|
130
|
+
"version": "0.0.1",
|
|
131
|
+
"main": "main.js"
|
|
132
|
+
}
|
|
133
|
+
`)
|
|
134
|
+
expect(
|
|
135
|
+
await readFile(join(tmpDir, 'test-author.test-extension/main.js'), 'utf-8')
|
|
136
|
+
).toBe('export const activate = () => { console.info("hello world") }')
|
|
137
|
+
expect(
|
|
138
|
+
await readFile(
|
|
139
|
+
join(tmpDir, 'test-author.test-extension/package.json'),
|
|
140
|
+
'utf-8'
|
|
141
|
+
)
|
|
142
|
+
).toBe('{ "type" : "module" }')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('install should fail when the server sends a bad status code', async () => {
|
|
146
|
+
handler = (request, response) => {
|
|
147
|
+
switch (request.url) {
|
|
148
|
+
default:
|
|
149
|
+
response.statusCode = 404
|
|
150
|
+
return response.end()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const tmpDir = await getTmpDir()
|
|
154
|
+
Platform.state.getExtensionsPath = () => tmpDir
|
|
155
|
+
await expect(
|
|
156
|
+
ExtensionManagement.install('test-author.test-extension')
|
|
157
|
+
).rejects.toThrowError(
|
|
158
|
+
/Failed to install extension "test-author.test-extension": Failed to download "http:\/\/localhost:\d+\/download\/test-author.test-extension": Response code 404 \(Not Found\)/
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('install should fail when the server sends an invalid compressed object', async () => {
|
|
163
|
+
// TODO avoid side effect in tests, use createServer
|
|
164
|
+
handler = (request, res) => {
|
|
165
|
+
switch (request.url) {
|
|
166
|
+
case '/download/test-author.test-extension':
|
|
167
|
+
res.statusCode = 200
|
|
168
|
+
return res.end('abc')
|
|
169
|
+
default:
|
|
170
|
+
res.statusCode = 404
|
|
171
|
+
return res.end()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const tmpDir = await getTmpDir()
|
|
175
|
+
Platform.state.getExtensionsPath = () => tmpDir
|
|
176
|
+
await expect(
|
|
177
|
+
ExtensionManagement.install('test-author.test-extension')
|
|
178
|
+
).rejects.toThrowError(
|
|
179
|
+
/^Failed to install extension "test-author.test-extension": unexpected end of file/
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('uninstall', async () => {
|
|
184
|
+
const tmpDir = await getTmpDir()
|
|
185
|
+
Platform.state.getExtensionsPath = () => tmpDir
|
|
186
|
+
await mkdir(join(tmpDir, 'test-author.test-extension'))
|
|
187
|
+
expect(await exists(join(tmpDir, 'test-author.test-extension'))).toBe(true)
|
|
188
|
+
await ExtensionManagement.uninstall('test-author.test-extension')
|
|
189
|
+
expect(await exists(join(tmpDir, 'test-author.test-extension'))).toBe(false)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test("uninstall should fail when extension doesn't exist", async () => {
|
|
193
|
+
const tmpDir = await getTmpDir()
|
|
194
|
+
Platform.state.getExtensionsPath = () => tmpDir
|
|
195
|
+
await expect(
|
|
196
|
+
ExtensionManagement.uninstall('test-author.test-extension')
|
|
197
|
+
).rejects.toThrowError(
|
|
198
|
+
/^Failed to uninstall extension "test-author.test-extension": ENOENT: no such file or directory/
|
|
199
|
+
)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// TODO test for extension main not found (extension host)
|
|
203
|
+
|
|
204
|
+
// TODO test for extension json not found (extension host)
|
|
205
|
+
|
|
206
|
+
// TODO test for extension code import fails (extension host)
|
|
207
|
+
// TODO test for extension activation fails (in extension host)
|
|
208
|
+
// TODO test for extension deactivation fails (in extension host)
|
|
209
|
+
// TODO test for global unhandlederror/unhandledrejection (in extension host)
|
|
210
|
+
|
|
211
|
+
test('getAllExtensions', async () => {
|
|
212
|
+
const tmpDir1 = await getTmpDir()
|
|
213
|
+
const manifestPath1 = join(tmpDir1, 'test-extension', 'extension.json')
|
|
214
|
+
await mkdir(dirname(manifestPath1))
|
|
215
|
+
await writeFile(manifestPath1, JSON.stringify({ id: 'test-extension' }))
|
|
216
|
+
const tmpDir2 = await getTmpDir()
|
|
217
|
+
const manifestPath2 = join(tmpDir2, 'builtin-extension', 'extension.json')
|
|
218
|
+
await mkdir(dirname(manifestPath2))
|
|
219
|
+
await writeFile(manifestPath2, JSON.stringify({ id: 'builtin-extension' }))
|
|
220
|
+
const tmpDir3 = await getTmpDir()
|
|
221
|
+
Platform.state.getExtensionsPath = () => tmpDir1
|
|
222
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir2
|
|
223
|
+
Platform.state.getDisabledExtensionsPath = () => tmpDir3
|
|
224
|
+
expect(await ExtensionManagement.getAllExtensions()).toEqual([
|
|
225
|
+
{
|
|
226
|
+
status: 'fulfilled',
|
|
227
|
+
id: 'builtin-extension',
|
|
228
|
+
path: join(tmpDir2, 'builtin-extension'),
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
status: 'fulfilled',
|
|
232
|
+
id: 'test-extension',
|
|
233
|
+
path: join(tmpDir1, 'test-extension'),
|
|
234
|
+
},
|
|
235
|
+
])
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('getAllExtensions - invalid extension.json', async () => {
|
|
239
|
+
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
240
|
+
const tmpDir1 = await getTmpDir()
|
|
241
|
+
const manifestPath = join(tmpDir1, 'test-extension', 'extension.json')
|
|
242
|
+
await mkdir(dirname(manifestPath))
|
|
243
|
+
await writeFile(manifestPath, '{invalid json}')
|
|
244
|
+
const tmpDir2 = await getTmpDir()
|
|
245
|
+
const tmpDir3 = await getTmpDir()
|
|
246
|
+
Platform.state.getExtensionsPath = () => tmpDir1
|
|
247
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir2
|
|
248
|
+
Platform.state.getDisabledExtensionsPath = () => tmpDir3
|
|
249
|
+
expect(await ExtensionManagement.getAllExtensions()).toEqual([
|
|
250
|
+
{
|
|
251
|
+
path: join(tmpDir1, 'test-extension'),
|
|
252
|
+
reason: new VError(
|
|
253
|
+
'Failed to load extension "test-extension": Failed to load extension manifest: JSON parsing error:'
|
|
254
|
+
),
|
|
255
|
+
status: 'rejected',
|
|
256
|
+
},
|
|
257
|
+
])
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('disable', async () => {
|
|
261
|
+
const tmpDir1 = await getTmpDir()
|
|
262
|
+
const tmpDir2 = await getTmpDir()
|
|
263
|
+
await mkdir(join(tmpDir1, 'test-extension'))
|
|
264
|
+
await writeFile(join(tmpDir1, 'test-extension', 'extension.json'), '{}')
|
|
265
|
+
Platform.state.getExtensionsPath = () => tmpDir1
|
|
266
|
+
Platform.state.getDisabledExtensionsPath = () => tmpDir2
|
|
267
|
+
await ExtensionManagement.disable('test-extension')
|
|
268
|
+
expect(await readdir(tmpDir1)).toEqual([])
|
|
269
|
+
expect(await readdir(tmpDir2)).toEqual(['test-extension'])
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('disable should fail if enabled extension path does not exist', async () => {
|
|
273
|
+
const tmpDir1 = await getTmpDir()
|
|
274
|
+
const tmpDir2 = await getTmpDir()
|
|
275
|
+
await mkdir(join(tmpDir1, 'test-extension'))
|
|
276
|
+
await writeFile(join(tmpDir1, 'test-extension', 'extension.json'), '{}')
|
|
277
|
+
Platform.state.getExtensionsPath = () => tmpDir1
|
|
278
|
+
Platform.state.getDisabledExtensionsPath = () => tmpDir2
|
|
279
|
+
const nonExistentPath1 = join(tmpDir1, 'non-existent-extension')
|
|
280
|
+
const nonExistentPath2 = join(tmpDir2, 'non-existent-extension')
|
|
281
|
+
await expect(
|
|
282
|
+
ExtensionManagement.disable('non-existent-extension')
|
|
283
|
+
).rejects.toThrowError(
|
|
284
|
+
`Failed to disable extension non-existent-extension: ENOENT: no such file or directory, rename '${nonExistentPath1}' -> '${nonExistentPath2}'`
|
|
285
|
+
)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test('getExtensions - empty object', async () => {
|
|
289
|
+
const tmpDir1 = await getTmpDir()
|
|
290
|
+
const tmpDir2 = await getTmpDir()
|
|
291
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
292
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), '{}')
|
|
293
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
294
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
295
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
296
|
+
{
|
|
297
|
+
status: 'fulfilled',
|
|
298
|
+
path: join(tmpDir1, '/test-extension-1'),
|
|
299
|
+
},
|
|
300
|
+
])
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test.skip('getExtensions - error - invalid value - empty array', async () => {
|
|
304
|
+
const tmpDir1 = await getTmpDir()
|
|
305
|
+
const tmpDir2 = await getTmpDir()
|
|
306
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
307
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), '[]')
|
|
308
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
309
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
310
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
311
|
+
{
|
|
312
|
+
status: 'fulfilled',
|
|
313
|
+
|
|
314
|
+
path: join(tmpDir1, 'test-extension-1'),
|
|
315
|
+
},
|
|
316
|
+
])
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
test('getExtensions - error - invalid value - null', async () => {
|
|
320
|
+
const tmpDir1 = await getTmpDir()
|
|
321
|
+
const tmpDir2 = await getTmpDir()
|
|
322
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
323
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), 'null')
|
|
324
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
325
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
326
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
327
|
+
{
|
|
328
|
+
status: 'rejected',
|
|
329
|
+
reason: new VError(
|
|
330
|
+
'Failed to load extension "test-extension-1": Failed to load extension manifest: Invalid manifest file: Not an JSON object.'
|
|
331
|
+
),
|
|
332
|
+
path: join(tmpDir1, 'test-extension-1'),
|
|
333
|
+
},
|
|
334
|
+
])
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('getExtensions - error - invalid value - string', async () => {
|
|
338
|
+
const tmpDir1 = await getTmpDir()
|
|
339
|
+
const tmpDir2 = await getTmpDir()
|
|
340
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
341
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), '""')
|
|
342
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
343
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
344
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
345
|
+
{
|
|
346
|
+
status: 'rejected',
|
|
347
|
+
reason: new VError(
|
|
348
|
+
'Failed to load extension "test-extension-1": Failed to load extension manifest: Invalid manifest file: Not an JSON object.'
|
|
349
|
+
),
|
|
350
|
+
path: join(tmpDir1, 'test-extension-1'),
|
|
351
|
+
},
|
|
352
|
+
])
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
test('getExtensions - error - invalid value - number', async () => {
|
|
356
|
+
const tmpDir1 = await getTmpDir()
|
|
357
|
+
const tmpDir2 = await getTmpDir()
|
|
358
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
359
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), '42')
|
|
360
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
361
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
362
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
363
|
+
{
|
|
364
|
+
status: 'rejected',
|
|
365
|
+
reason: new VError(
|
|
366
|
+
'Failed to load extension "test-extension-1": Failed to load extension manifest: Invalid manifest file: Not an JSON object.'
|
|
367
|
+
),
|
|
368
|
+
path: join(tmpDir1, 'test-extension-1'),
|
|
369
|
+
},
|
|
370
|
+
])
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test('getExtensions - error - invalid value - boolean', async () => {
|
|
374
|
+
const tmpDir1 = await getTmpDir()
|
|
375
|
+
const tmpDir2 = await getTmpDir()
|
|
376
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
377
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), 'true')
|
|
378
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
379
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
380
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
381
|
+
{
|
|
382
|
+
status: 'rejected',
|
|
383
|
+
reason: new VError(
|
|
384
|
+
'Failed to load extension "test-extension-1": Failed to load extension manifest: Invalid manifest file: Not an JSON object.'
|
|
385
|
+
),
|
|
386
|
+
path: join(tmpDir1, 'test-extension-1'),
|
|
387
|
+
},
|
|
388
|
+
])
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
// TODO should have better error message here and also stack
|
|
392
|
+
test('getExtensions - error - invalid json', async () => {
|
|
393
|
+
const tmpDir1 = await getTmpDir()
|
|
394
|
+
const tmpDir2 = await getTmpDir()
|
|
395
|
+
await mkdir(join(tmpDir1, 'test-extension-1'))
|
|
396
|
+
await writeFile(join(tmpDir1, 'test-extension-1', 'extension.json'), '{')
|
|
397
|
+
Platform.state.getBuiltinExtensionsPath = () => tmpDir1
|
|
398
|
+
Platform.state.getExtensionsPath = () => tmpDir2
|
|
399
|
+
expect(await ExtensionManagement.getExtensions()).toEqual([
|
|
400
|
+
{
|
|
401
|
+
reason: new VError(
|
|
402
|
+
'Failed to load extension "test-extension-1": Failed to load extension manifest: Unexpected end of JSON input while parsing "{"'
|
|
403
|
+
),
|
|
404
|
+
status: 'rejected',
|
|
405
|
+
path: join(tmpDir1, 'test-extension-1'),
|
|
406
|
+
},
|
|
407
|
+
])
|
|
408
|
+
})
|