@lvce-editor/shared-process 0.5.0 → 0.6.1
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/config/builtinCommands.json +15 -2
- package/config/defaultKeyBindings.json +33 -0
- package/config/defaultSettings.json +3 -1
- package/extensions/builtin.language-basics-docker/extension.json +1 -1
- package/extensions/builtin.language-basics-docker/src/tokenizeDockerfile.js +29 -11
- package/extensions/builtin.language-basics-html/src/tokenizeHtml.js +7 -2
- package/extensions/builtin.language-basics-json/extension.json +1 -1
- package/extensions/builtin.language-basics-nvmrc/README.md +14 -0
- package/extensions/builtin.language-basics-nvmrc/extension.json +12 -0
- package/extensions/builtin.language-basics-nvmrc/src/tokenizeNvmrc.js +30 -0
- package/extensions/builtin.language-basics-plaintext/src/tokenizePlaintext.js +4 -7
- package/extensions/builtin.language-basics-rust/README.md +14 -0
- package/extensions/builtin.language-basics-rust/extension.json +12 -0
- package/extensions/builtin.language-basics-rust/src/tokenizeRust.js +158 -0
- package/extensions/builtin.language-basics-toml/extension.json +1 -1
- package/extensions/builtin.language-basics-toml/src/tokenizeToml.js +93 -8
- package/extensions/builtin.language-basics-yaml/src/tokenizeYaml.js +10 -3
- package/package.json +6 -5
- package/src/parts/Cli/Cli.js +24 -0
- package/src/parts/CliInstall/CliInstall.js +10 -0
- package/src/parts/CliLink/CliLink.js +6 -0
- package/src/parts/CliList/CliList.js +15 -0
- package/src/parts/Download/Download.js +5 -13
- package/src/parts/DownloadAndExtract/DownloadAndExtract.js +14 -0
- package/src/parts/Error/FileSystemError.js +10 -0
- package/src/parts/ExtensionInstall/ExtensionInstall.ipc.js +6 -0
- package/src/parts/ExtensionInstall/ExtensionInstall.js +25 -0
- package/src/parts/ExtensionInstallFromGitHub/ExtensionInstallFromGitHub.js +34 -0
- package/src/parts/ExtensionInstallFromUrl/ExtensionInstallFromUrl.js +32 -0
- package/src/parts/ExtensionInstallParseInput/ExtensionInstallParseInput.js +84 -0
- package/src/parts/ExtensionLink/ExtensionLink.js +32 -0
- package/src/parts/ExtensionList/ExtensionList.js +66 -0
- package/src/parts/ExtensionManagement/ExtensionManagement.js +4 -3
- package/src/parts/ExtensionUninstall/ExtensionUninstall.js +0 -0
- package/src/parts/Extract/Extract.js +34 -0
- package/src/parts/FileSystem/FileSystem.js +12 -0
- package/src/parts/FileSystemErrorCodes/FileSystemErrorCodes.js +4 -0
- package/src/parts/Path/Path.js +4 -0
- package/src/parts/Platform/Platform.js +6 -3
- package/src/parts/Preferences/Preferences.js +16 -1
- package/src/parts/SymLink/SymLink.js +15 -0
- package/src/parts/TmpFile/TmpFile.js +15 -0
- package/src/sharedProcessMain.js +16 -5
|
@@ -66,7 +66,7 @@ const RE_SEMICOLON = /^;/
|
|
|
66
66
|
const RE_COMMA = /^,/
|
|
67
67
|
const RE_ANYTHING = /^.*/
|
|
68
68
|
const RE_ANYTHING_BUT_COMMENT = /^[^#]+/
|
|
69
|
-
const RE_NUMERIC = /^(([0-9]+\.?[0-9]*)|(\.[0-9]+))
|
|
69
|
+
const RE_NUMERIC = /^(([0-9]+\.?[0-9]*)|(\.[0-9]+))(?=\s|$)/
|
|
70
70
|
const RE_ANYTHING_UNTIL_CLOSE_BRACE = /^[^\}]+/
|
|
71
71
|
const RE_BLOCK_COMMENT_START = /^\/\*/
|
|
72
72
|
const RE_BLOCK_COMMENT_END = /^\*\//
|
|
@@ -86,7 +86,7 @@ const RE_LANGUAGE_CONSTANT = /^(?:true|false|null)(?!#)/
|
|
|
86
86
|
const RE_COLON = /^:/
|
|
87
87
|
const RE_DASH = /^\-/
|
|
88
88
|
const RE_WORDS = /^[\w\s]*\w/
|
|
89
|
-
const RE_MULTI_LINE_STRING_START = /^(
|
|
89
|
+
const RE_MULTI_LINE_STRING_START = /^(?:\||>\-|>)/
|
|
90
90
|
const RE_KEY_PRE = /^\s*(\-\s*)?/
|
|
91
91
|
|
|
92
92
|
export const initialLineState = {
|
|
@@ -188,7 +188,6 @@ export const tokenizeLine = (line, lineState) => {
|
|
|
188
188
|
token = TokenType.Comment
|
|
189
189
|
state = State.InsideLineComment
|
|
190
190
|
} else if ((next = part.match(RE_MULTI_LINE_STRING_START))) {
|
|
191
|
-
part
|
|
192
191
|
token = TokenType.Punctuation
|
|
193
192
|
state = State.AfterPipe
|
|
194
193
|
} else if ((next = part.match(RE_PROPERTY_VALUE_1))) {
|
|
@@ -234,6 +233,9 @@ export const tokenizeLine = (line, lineState) => {
|
|
|
234
233
|
} else if ((next = part.match(RE_NUMERIC))) {
|
|
235
234
|
token = TokenType.Numeric
|
|
236
235
|
state = State.TopLevelContent
|
|
236
|
+
} else if ((next = part.match(RE_MULTI_LINE_STRING_START))) {
|
|
237
|
+
token = TokenType.Punctuation
|
|
238
|
+
state = State.AfterPipe
|
|
237
239
|
} else if ((next = part.match(RE_ANYTHING))) {
|
|
238
240
|
token = TokenType.PropertyValueString
|
|
239
241
|
state = State.TopLevelContent
|
|
@@ -279,6 +281,9 @@ export const tokenizeLine = (line, lineState) => {
|
|
|
279
281
|
} else if ((next = part.match(RE_PROPERTY_NAME))) {
|
|
280
282
|
token = TokenType.PropertyName
|
|
281
283
|
state = State.AfterPropertyName
|
|
284
|
+
} else if ((next = part.match(RE_DASH))) {
|
|
285
|
+
token = TokenType.Punctuation
|
|
286
|
+
state = State.AfterDash
|
|
282
287
|
} else if ((next = part.match(RE_ANYTHING))) {
|
|
283
288
|
token = TokenType.Text
|
|
284
289
|
state = State.TopLevelContent
|
|
@@ -308,6 +313,8 @@ export const tokenizeLine = (line, lineState) => {
|
|
|
308
313
|
break
|
|
309
314
|
case State.AfterPipe:
|
|
310
315
|
keyOffset = getKeyOffset(line)
|
|
316
|
+
keyOffset
|
|
317
|
+
line
|
|
311
318
|
state = State.InsideMultiLineString
|
|
312
319
|
break
|
|
313
320
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/shared-process",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"node": ">=16"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@lvce-editor/extension-host": "0.6.1",
|
|
24
|
+
"@lvce-editor/pty-host": "0.6.1",
|
|
23
25
|
"chokidar": "^3.5.3",
|
|
24
26
|
"debug": "^4.3.4",
|
|
25
27
|
"execa": "^6.1.0",
|
|
@@ -34,12 +36,11 @@
|
|
|
34
36
|
"parse-json": "^6.0.2",
|
|
35
37
|
"tail": "^2.2.4",
|
|
36
38
|
"tar-fs": "^2.1.1",
|
|
39
|
+
"tmp-promise": "^3.0.3",
|
|
37
40
|
"trash": "^8.1.0",
|
|
38
41
|
"verror": "^1.10.1",
|
|
39
|
-
"ws": "^8.
|
|
40
|
-
"xdg-basedir": "^5.1.0"
|
|
41
|
-
"@lvce-editor/pty-host": "0.5.0",
|
|
42
|
-
"@lvce-editor/extension-host": "0.5.0"
|
|
42
|
+
"ws": "^8.9.0",
|
|
43
|
+
"xdg-basedir": "^5.1.0"
|
|
43
44
|
},
|
|
44
45
|
"optionalDependencies": {
|
|
45
46
|
"electron-clipboard-ex": "^1.3.3",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const getModule = (argv0) => {
|
|
2
|
+
switch (argv0) {
|
|
3
|
+
case 'install':
|
|
4
|
+
return import('../CliInstall/CliInstall.js')
|
|
5
|
+
case 'list':
|
|
6
|
+
return import('../CliList/CliList.js')
|
|
7
|
+
case 'link':
|
|
8
|
+
return import('../CliLink/CliLink.js')
|
|
9
|
+
default:
|
|
10
|
+
throw new Error('command not found')
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const handleCliArgs = async (argv, console, process) => {
|
|
15
|
+
const argv0 = argv[0]
|
|
16
|
+
const module = await getModule(argv0)
|
|
17
|
+
// console.log('handle cli args')
|
|
18
|
+
try {
|
|
19
|
+
await module.handleCliArgs(argv, console, process)
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(error)
|
|
22
|
+
process.exitCode = 1
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as ExtensionInstall from '../ExtensionInstall/ExtensionInstall.js'
|
|
2
|
+
|
|
3
|
+
export const handleCliArgs = async (argv, console) => {
|
|
4
|
+
const extension = argv[1]
|
|
5
|
+
if (!extension) {
|
|
6
|
+
console.error('extension argument is required')
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
await ExtensionInstall.install(extension)
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as ExtensionList from '../ExtensionList/ExtensionList.js'
|
|
2
|
+
|
|
3
|
+
const getOutputLine = (extension) => {
|
|
4
|
+
return `${extension.id}: ${extension.version}`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const getOutput = (extensions) => {
|
|
8
|
+
return extensions.map(getOutputLine).join('\n')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const handleCliArgs = async (argv, console) => {
|
|
12
|
+
const extensions = await ExtensionList.list()
|
|
13
|
+
const output = getOutput(extensions)
|
|
14
|
+
console.info(output)
|
|
15
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import got from 'got'
|
|
2
|
-
import {
|
|
1
|
+
import got, { RequestError } from 'got'
|
|
2
|
+
import { createWriteStream } from 'node:fs'
|
|
3
3
|
import { mkdir, rm } from 'node:fs/promises'
|
|
4
4
|
import { pipeline } from 'node:stream/promises'
|
|
5
|
-
import { createBrotliDecompress } from 'node:zlib'
|
|
6
|
-
import tar from 'tar-fs'
|
|
7
5
|
import VError from 'verror'
|
|
8
6
|
import * as Path from '../Path/Path.js'
|
|
9
7
|
|
|
@@ -17,15 +15,9 @@ export const download = async (url, outFile) => {
|
|
|
17
15
|
} catch {
|
|
18
16
|
// ignore
|
|
19
17
|
}
|
|
18
|
+
if (error instanceof RequestError) {
|
|
19
|
+
throw new VError(`Failed to download "${url}": ${error.message}`)
|
|
20
|
+
}
|
|
20
21
|
throw new VError(error, `Failed to download "${url}"`)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
export const extract = async (inFile, outDir) => {
|
|
25
|
-
await mkdir(outDir, { recursive: true })
|
|
26
|
-
await pipeline(
|
|
27
|
-
createReadStream(inFile),
|
|
28
|
-
createBrotliDecompress(),
|
|
29
|
-
tar.extract(outDir)
|
|
30
|
-
)
|
|
31
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import got from 'got'
|
|
2
|
+
import { pipeline } from 'stream/promises'
|
|
3
|
+
import tar from 'tar-fs'
|
|
4
|
+
import { createGunzip } from 'zlib'
|
|
5
|
+
|
|
6
|
+
export const downloadAndExtractTarGz = async ({ url, outDir, strip }) => {
|
|
7
|
+
await pipeline(
|
|
8
|
+
got.stream(url),
|
|
9
|
+
createGunzip(),
|
|
10
|
+
tar.extract(outDir, {
|
|
11
|
+
strip,
|
|
12
|
+
})
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as ExtensionInstallParseInput from '../ExtensionInstallParseInput/ExtensionInstallParseInput.js'
|
|
2
|
+
|
|
3
|
+
const getModule = (type) => {
|
|
4
|
+
switch (type) {
|
|
5
|
+
case ExtensionInstallParseInput.InstallType.GithubRepository:
|
|
6
|
+
return import(
|
|
7
|
+
'../ExtensionInstallFromGitHub/ExtensionInstallFromGitHub.js'
|
|
8
|
+
)
|
|
9
|
+
case ExtensionInstallParseInput.InstallType.Url:
|
|
10
|
+
return import('../ExtensionInstallFromUrl/ExtensionInstallFromUrl.js')
|
|
11
|
+
default:
|
|
12
|
+
throw new Error('module not found')
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const install = async (input) => {
|
|
17
|
+
const parsed = ExtensionInstallParseInput.parse(input)
|
|
18
|
+
if (parsed.type === ExtensionInstallParseInput.InstallType.ParsingError) {
|
|
19
|
+
throw new Error(`Failed to parse input: ${parsed.options.message}`)
|
|
20
|
+
}
|
|
21
|
+
console.log({ parsed })
|
|
22
|
+
const module = await getModule(parsed.type)
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
await module.install(parsed.options)
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import VError from 'verror'
|
|
2
|
+
import * as DownloadAndExtract from '../DownloadAndExtract/DownloadAndExtract.js'
|
|
3
|
+
import * as FileSystem from '../FileSystem/FileSystem.js'
|
|
4
|
+
import * as Path from '../Path/Path.js'
|
|
5
|
+
import * as Platform from '../Platform/Platform.js'
|
|
6
|
+
|
|
7
|
+
export const install = async ({ user, repo, branch }) => {
|
|
8
|
+
try {
|
|
9
|
+
const cachedExtensionsPath = Platform.getCachedExtensionsPath()
|
|
10
|
+
const url = `https://codeload.github.com/${user}/${repo}/tar.gz/${branch}`
|
|
11
|
+
const cachedExtensionPath = Path.join(
|
|
12
|
+
cachedExtensionsPath,
|
|
13
|
+
`github-${user}-${repo}-${branch}`
|
|
14
|
+
)
|
|
15
|
+
await DownloadAndExtract.downloadAndExtractTarGz({
|
|
16
|
+
url,
|
|
17
|
+
outDir: cachedExtensionPath,
|
|
18
|
+
strip: 1,
|
|
19
|
+
})
|
|
20
|
+
const extensionsPath = Platform.getExtensionsPath()
|
|
21
|
+
const manifestPath = Path.join(cachedExtensionPath, 'extension.json')
|
|
22
|
+
const manifestContent = await FileSystem.readFile(manifestPath)
|
|
23
|
+
const manifestJson = JSON.parse(manifestContent)
|
|
24
|
+
const id = manifestJson.id
|
|
25
|
+
if (!id) {
|
|
26
|
+
throw new Error('missing id in extension manifest')
|
|
27
|
+
}
|
|
28
|
+
const outDir = Path.join(extensionsPath, id)
|
|
29
|
+
await FileSystem.remove(outDir)
|
|
30
|
+
await FileSystem.rename(cachedExtensionPath, outDir)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new VError(error, `Failed to install ${user}/${repo}`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFile, rename, rm } from 'fs/promises'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import VError from 'verror'
|
|
4
|
+
import * as Download from '../Download/Download.js'
|
|
5
|
+
import * as Extract from '../Extract/Extract.js'
|
|
6
|
+
import * as Platform from '../Platform/Platform.js'
|
|
7
|
+
import * as TmpFile from '../TmpFile/TmpFile.js'
|
|
8
|
+
|
|
9
|
+
export const install = async ({ url }) => {
|
|
10
|
+
try {
|
|
11
|
+
// TODO cache extension tar br file, probably by url <cachedExtensions>/user/repo/releases/<tag>/<fileName>
|
|
12
|
+
// const cachedExtensionsPath = Platform.getCachedExtensionsPath()
|
|
13
|
+
// const outFile = join(cachedExtensionsPath, 'installed-extension.tar.br')
|
|
14
|
+
const extensionsPath = Platform.getExtensionsPath()
|
|
15
|
+
const tmpFile = await TmpFile.getTmpFile()
|
|
16
|
+
await Download.download(url, tmpFile)
|
|
17
|
+
const tmpDir = await TmpFile.getTmpDir()
|
|
18
|
+
await Extract.extractTarBr(tmpFile, tmpDir)
|
|
19
|
+
const manifestPath = join(tmpDir, 'extension.json')
|
|
20
|
+
const manifestContent = await readFile(manifestPath, 'utf8')
|
|
21
|
+
const manifestJson = JSON.parse(manifestContent)
|
|
22
|
+
const id = manifestJson.id
|
|
23
|
+
if (!id) {
|
|
24
|
+
throw new Error('missing id in extension manifest')
|
|
25
|
+
}
|
|
26
|
+
const outDir = join(extensionsPath, id)
|
|
27
|
+
await rm(outDir, { recursive: true, force: true })
|
|
28
|
+
await rename(tmpDir, outDir)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new VError(error, `Failed to install "${url}"`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const InstallType = {
|
|
2
|
+
Url: 1,
|
|
3
|
+
GithubRepository: 2,
|
|
4
|
+
ParsingError: 3,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const parseUrlGithub = (input) => {
|
|
8
|
+
const parts = input.split('/')
|
|
9
|
+
const slashCount = parts.length
|
|
10
|
+
if (slashCount === 5) {
|
|
11
|
+
const user = parts[3]
|
|
12
|
+
const repo = parts[4]
|
|
13
|
+
return {
|
|
14
|
+
type: InstallType.GithubRepository,
|
|
15
|
+
options: {
|
|
16
|
+
user,
|
|
17
|
+
repo,
|
|
18
|
+
branch: 'HEAD',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (slashCount === 7) {
|
|
23
|
+
const user = parts[3]
|
|
24
|
+
const repo = parts[4]
|
|
25
|
+
const type = parts[5]
|
|
26
|
+
const commit = parts[6]
|
|
27
|
+
if (type === 'tree') {
|
|
28
|
+
return {
|
|
29
|
+
type: InstallType.GithubRepository,
|
|
30
|
+
options: {
|
|
31
|
+
user,
|
|
32
|
+
repo,
|
|
33
|
+
branch: commit,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (type === 'pull') {
|
|
38
|
+
return {
|
|
39
|
+
type: InstallType.ParsingError,
|
|
40
|
+
options: {
|
|
41
|
+
message: 'Cannot download from Pull Request',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
type: InstallType.ParsingError,
|
|
48
|
+
options: {
|
|
49
|
+
message: 'Failed to parse github url',
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parseUrl = (input) => {
|
|
55
|
+
if (input.startsWith('https://github.com')) {
|
|
56
|
+
return parseUrlGithub(input)
|
|
57
|
+
}
|
|
58
|
+
if (input.endsWith('.tar.br')) {
|
|
59
|
+
return {
|
|
60
|
+
type: InstallType.Url,
|
|
61
|
+
options: {
|
|
62
|
+
url: input,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
type: InstallType.ParsingError,
|
|
68
|
+
options: {
|
|
69
|
+
message: 'Failed to parse url',
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const parse = (input) => {
|
|
75
|
+
if (input.startsWith('https://')) {
|
|
76
|
+
return parseUrl(input)
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
type: InstallType.ParsingError,
|
|
80
|
+
options: {
|
|
81
|
+
message: 'Failed to parse input',
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import VError from 'verror'
|
|
2
|
+
import * as FileSystem from '../FileSystem/FileSystem.js'
|
|
3
|
+
import * as FileSystemErrorCodes from '../FileSystemErrorCodes/FileSystemErrorCodes.js'
|
|
4
|
+
import * as Path from '../Path/Path.js'
|
|
5
|
+
import * as Platform from '../Platform/Platform.js'
|
|
6
|
+
import * as SymLink from '../SymLink/SymLink.js'
|
|
7
|
+
|
|
8
|
+
const linkFallBack = async (path) => {
|
|
9
|
+
try {
|
|
10
|
+
const extensionsPath = Platform.getExtensionsPath()
|
|
11
|
+
const baseName = Path.basename(path)
|
|
12
|
+
const to = Path.join(extensionsPath, baseName)
|
|
13
|
+
await FileSystem.remove(to)
|
|
14
|
+
await SymLink.createSymLink(path, to)
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new VError(error, `Failed to link extension`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const link = async (path) => {
|
|
21
|
+
try {
|
|
22
|
+
const extensionsPath = Platform.getExtensionsPath()
|
|
23
|
+
const baseName = Path.basename(path)
|
|
24
|
+
const to = Path.join(extensionsPath, baseName)
|
|
25
|
+
await SymLink.createSymLink(path, to)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error && error.code === FileSystemErrorCodes.EEXIST) {
|
|
28
|
+
return linkFallBack(path)
|
|
29
|
+
}
|
|
30
|
+
throw new VError(error, `Failed to link extension`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
2
|
+
import { basename, dirname, join } from 'node:path'
|
|
3
|
+
import VError from 'verror'
|
|
4
|
+
import * as Platform from '../Platform/Platform.js'
|
|
5
|
+
import * as JsonFile from '../JsonFile/JsonFile.js'
|
|
6
|
+
|
|
7
|
+
const extensionPath = Platform.getExtensionsPath()
|
|
8
|
+
|
|
9
|
+
const getAbsolutePath = (dirent) => {
|
|
10
|
+
return join(extensionPath, dirent)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const getManifestVersion = (json) => {
|
|
14
|
+
if (json && json.version && typeof json.version === 'string') {
|
|
15
|
+
return json.version
|
|
16
|
+
}
|
|
17
|
+
return 'n/a'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getManifestId = (json, jsonPath) => {
|
|
21
|
+
if (json && json.id && typeof json.id === 'string') {
|
|
22
|
+
return json.id
|
|
23
|
+
}
|
|
24
|
+
return basename(jsonPath)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getManifestInfo = async (extensionPath) => {
|
|
28
|
+
const manifestPath = join(extensionPath, 'extension.json')
|
|
29
|
+
const json = await JsonFile.readJson(manifestPath)
|
|
30
|
+
const id = getManifestId(json, extensionPath)
|
|
31
|
+
const version = getManifestVersion(json)
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
version,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isFulfilled = (result) => {
|
|
39
|
+
return result.status === 'fulfilled'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getValue = (result) => {
|
|
43
|
+
return result.value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getManifestInfos = async (manifestPaths) => {
|
|
47
|
+
const manifestInfos = await Promise.allSettled(
|
|
48
|
+
manifestPaths.map(getManifestInfo)
|
|
49
|
+
)
|
|
50
|
+
const results = manifestInfos.filter(isFulfilled).map(getValue)
|
|
51
|
+
return results
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const list = async () => {
|
|
55
|
+
try {
|
|
56
|
+
const dirents = await readdir(extensionPath)
|
|
57
|
+
const extensionPaths = dirents.map(getAbsolutePath)
|
|
58
|
+
const infos = await getManifestInfos(extensionPaths)
|
|
59
|
+
return infos
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error && error.code === 'ENOENT') {
|
|
62
|
+
return []
|
|
63
|
+
}
|
|
64
|
+
throw new VError(error, `Failed to list extensions`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -36,7 +36,8 @@ export const install = async (id) => {
|
|
|
36
36
|
// TODO this should be a stateless function, renderer-worker should have info on marketplace url
|
|
37
37
|
// TODO use command.execute
|
|
38
38
|
try {
|
|
39
|
-
const { download
|
|
39
|
+
const { download } = await import('../Download/Download.js')
|
|
40
|
+
const { extractTarBr } = await import('../Extract/Extract.js')
|
|
40
41
|
const marketplaceUrl = Platform.getMarketplaceUrl()
|
|
41
42
|
Debug.debug(`ExtensionManagement#install ${id}`)
|
|
42
43
|
const cachedExtensionsPath = Platform.getCachedExtensionsPath()
|
|
@@ -50,7 +51,7 @@ export const install = async (id) => {
|
|
|
50
51
|
Path.join(cachedExtensionsPath, `${id}.tar.br`)
|
|
51
52
|
)
|
|
52
53
|
Debug.debug(`ExtensionManagement#extract ${id}`)
|
|
53
|
-
await
|
|
54
|
+
await extractTarBr(
|
|
54
55
|
Path.join(cachedExtensionsPath, `${id}.tar.br`),
|
|
55
56
|
Path.join(extensionsPath, id)
|
|
56
57
|
)
|
|
@@ -258,7 +259,7 @@ export const getExtensions = async () => {
|
|
|
258
259
|
return [...builtinExtensions, ...installedExtensions]
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
const RE_THEME = /
|
|
262
|
+
const RE_THEME = /theme-[a-z\d\-]+$/
|
|
262
263
|
const isTheme = (extensionId) => {
|
|
263
264
|
return RE_THEME.test(extensionId)
|
|
264
265
|
}
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs'
|
|
2
|
+
import { mkdir } from 'node:fs/promises'
|
|
3
|
+
import { pipeline } from 'node:stream/promises'
|
|
4
|
+
import { createBrotliDecompress, createGunzip } from 'node:zlib'
|
|
5
|
+
import tar from 'tar-fs'
|
|
6
|
+
import VError from 'verror'
|
|
7
|
+
|
|
8
|
+
export const extractTarBr = async (inFile, outDir) => {
|
|
9
|
+
try {
|
|
10
|
+
await mkdir(outDir, { recursive: true })
|
|
11
|
+
await pipeline(
|
|
12
|
+
createReadStream(inFile),
|
|
13
|
+
createBrotliDecompress(),
|
|
14
|
+
tar.extract(outDir)
|
|
15
|
+
)
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new VError(error, `Failed to extract ${inFile}`)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const extractTarGz = async ({ inFile, outDir, strip }) => {
|
|
22
|
+
try {
|
|
23
|
+
await mkdir(outDir, { recursive: true })
|
|
24
|
+
await pipeline(
|
|
25
|
+
createReadStream(inFile),
|
|
26
|
+
createGunzip(),
|
|
27
|
+
tar.extract(outDir, {
|
|
28
|
+
strip,
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new VError(error, `Failed to extract ${inFile}`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -185,10 +185,22 @@ export const mkdir = async (path) => {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
const fallbackRename = async (oldPath, newPath) => {
|
|
189
|
+
try {
|
|
190
|
+
await fs.cp(oldPath, newPath, { recursive: true })
|
|
191
|
+
await fs.rm(oldPath, { recursive: true })
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new VError(error, `Failed to rename "${oldPath}" to "${newPath}"`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
188
197
|
export const rename = async (oldPath, newPath) => {
|
|
189
198
|
try {
|
|
190
199
|
await fs.rename(oldPath, newPath)
|
|
191
200
|
} catch (error) {
|
|
201
|
+
if (error && error.code === FileSystemErrorCodes.EXDEV) {
|
|
202
|
+
return fallbackRename(oldPath, newPath)
|
|
203
|
+
}
|
|
192
204
|
throw new VError(error, `Failed to rename "${oldPath}" to "${newPath}"`)
|
|
193
205
|
}
|
|
194
206
|
}
|
package/src/parts/Path/Path.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extensionHostPath } from '@lvce-editor/extension-host'
|
|
2
2
|
import { homedir, tmpdir } from 'node:os'
|
|
3
|
-
import { sep } from 'node:path'
|
|
3
|
+
import { resolve, sep } from 'node:path'
|
|
4
4
|
import { pathToFileURL } from 'node:url'
|
|
5
5
|
import { xdgCache, xdgConfig, xdgData, xdgState } from 'xdg-basedir'
|
|
6
6
|
import * as Path from '../Path/Path.js'
|
|
@@ -94,7 +94,6 @@ export const getRecentlyOpenedPath = () => {
|
|
|
94
94
|
export const getDefaultSettingsPath = () => {
|
|
95
95
|
return Path.join(Root.root, 'config', 'defaultSettings.json')
|
|
96
96
|
}
|
|
97
|
-
|
|
98
97
|
export const setEnvironmentVariables = (variables) => {
|
|
99
98
|
for (const [key, value] of Object.entries(variables)) {
|
|
100
99
|
process.env[key] = value
|
|
@@ -122,7 +121,11 @@ export const getTmpDir = () => {
|
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
export const getOnlyExtensionPath = () => {
|
|
125
|
-
|
|
124
|
+
const onlyExtensionPath = process.env.ONLY_EXTENSION
|
|
125
|
+
if (onlyExtensionPath) {
|
|
126
|
+
return resolve(onlyExtensionPath)
|
|
127
|
+
}
|
|
128
|
+
return ''
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
export const getAppDir = () => {
|
|
@@ -37,6 +37,16 @@ export const getDefaultPreferences = async () => {
|
|
|
37
37
|
// TODO efficiently load preferences -> first load cached preferences
|
|
38
38
|
// -> on idle check preferences
|
|
39
39
|
|
|
40
|
+
const getOverrides = () => {
|
|
41
|
+
const argvSliced = process.argv.slice(2)
|
|
42
|
+
const overrides = {}
|
|
43
|
+
for (const argv of argvSliced) {
|
|
44
|
+
if (argv.startsWith('--theme=')) {
|
|
45
|
+
overrides['workbench.colorTheme'] = argv.slice('--theme='.length)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return overrides
|
|
49
|
+
}
|
|
40
50
|
// TODO compare with timestamp/hash that preferences are fresh
|
|
41
51
|
export const getAll = async () => {
|
|
42
52
|
try {
|
|
@@ -50,8 +60,13 @@ export const getAll = async () => {
|
|
|
50
60
|
// }
|
|
51
61
|
const defaultPreferences = await getDefaultPreferences()
|
|
52
62
|
const userPreferences = await getUserPreferences()
|
|
63
|
+
const overrides = getOverrides()
|
|
53
64
|
// TODO separate backend and frontend preferences, ui only needs frontend preferences
|
|
54
|
-
const preferences = {
|
|
65
|
+
const preferences = {
|
|
66
|
+
...defaultPreferences,
|
|
67
|
+
...userPreferences,
|
|
68
|
+
...overrides,
|
|
69
|
+
}
|
|
55
70
|
// try {
|
|
56
71
|
// await mkdir(dirname(CACHED_SETTINGS_PATH), { recursive: true })
|
|
57
72
|
// await writeFile(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { symlink } from 'node:fs/promises'
|
|
2
|
+
import { FileSystemError } from '../Error/FileSystemError.js'
|
|
3
|
+
|
|
4
|
+
// TODO maybe move this to FileSystem module
|
|
5
|
+
|
|
6
|
+
export const createSymLink = async (target, path) => {
|
|
7
|
+
try {
|
|
8
|
+
await symlink(target, path)
|
|
9
|
+
} catch (error) {
|
|
10
|
+
throw new FileSystemError(
|
|
11
|
+
error,
|
|
12
|
+
`Failed to create symbolic link from ${target} to ${path}`
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
}
|