@lvce-editor/shared-process 0.5.0 → 0.6.0

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.
Files changed (42) hide show
  1. package/config/builtinCommands.json +15 -2
  2. package/config/defaultKeyBindings.json +33 -0
  3. package/config/defaultSettings.json +3 -1
  4. package/extensions/builtin.language-basics-docker/extension.json +1 -1
  5. package/extensions/builtin.language-basics-docker/src/tokenizeDockerfile.js +29 -11
  6. package/extensions/builtin.language-basics-html/src/tokenizeHtml.js +7 -2
  7. package/extensions/builtin.language-basics-json/extension.json +1 -1
  8. package/extensions/builtin.language-basics-nvmrc/README.md +14 -0
  9. package/extensions/builtin.language-basics-nvmrc/extension.json +12 -0
  10. package/extensions/builtin.language-basics-nvmrc/src/tokenizeNvmrc.js +30 -0
  11. package/extensions/builtin.language-basics-plaintext/src/tokenizePlaintext.js +4 -7
  12. package/extensions/builtin.language-basics-rust/README.md +14 -0
  13. package/extensions/builtin.language-basics-rust/extension.json +12 -0
  14. package/extensions/builtin.language-basics-rust/src/tokenizeRust.js +158 -0
  15. package/extensions/builtin.language-basics-toml/extension.json +1 -1
  16. package/extensions/builtin.language-basics-toml/src/tokenizeToml.js +93 -8
  17. package/extensions/builtin.language-basics-yaml/src/tokenizeYaml.js +10 -3
  18. package/package.json +6 -5
  19. package/src/parts/Cli/Cli.js +24 -0
  20. package/src/parts/CliInstall/CliInstall.js +10 -0
  21. package/src/parts/CliLink/CliLink.js +6 -0
  22. package/src/parts/CliList/CliList.js +15 -0
  23. package/src/parts/Download/Download.js +5 -13
  24. package/src/parts/DownloadAndExtract/DownloadAndExtract.js +14 -0
  25. package/src/parts/Error/FileSystemError.js +10 -0
  26. package/src/parts/ExtensionInstall/ExtensionInstall.ipc.js +6 -0
  27. package/src/parts/ExtensionInstall/ExtensionInstall.js +25 -0
  28. package/src/parts/ExtensionInstallFromGitHub/ExtensionInstallFromGitHub.js +34 -0
  29. package/src/parts/ExtensionInstallFromUrl/ExtensionInstallFromUrl.js +32 -0
  30. package/src/parts/ExtensionInstallParseInput/ExtensionInstallParseInput.js +84 -0
  31. package/src/parts/ExtensionLink/ExtensionLink.js +32 -0
  32. package/src/parts/ExtensionList/ExtensionList.js +66 -0
  33. package/src/parts/ExtensionManagement/ExtensionManagement.js +4 -3
  34. package/src/parts/ExtensionUninstall/ExtensionUninstall.js +0 -0
  35. package/src/parts/Extract/Extract.js +34 -0
  36. package/src/parts/FileSystem/FileSystem.js +12 -0
  37. package/src/parts/FileSystemErrorCodes/FileSystemErrorCodes.js +4 -0
  38. package/src/parts/Path/Path.js +4 -0
  39. package/src/parts/Preferences/Preferences.js +17 -1
  40. package/src/parts/SymLink/SymLink.js +15 -0
  41. package/src/parts/TmpFile/TmpFile.js +15 -0
  42. 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]+))\b/
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.5.0",
3
+ "version": "0.6.0",
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.0",
24
+ "@lvce-editor/pty-host": "0.6.0",
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.8.1",
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,6 @@
1
+ import * as ExtensionLink from '../ExtensionLink/ExtensionLink.js'
2
+
3
+ export const handleCliArgs = async (argv, console) => {
4
+ const path = argv.length > 1 ? argv[1] : process.cwd()
5
+ await ExtensionLink.link(path)
6
+ }
@@ -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 { createReadStream, createWriteStream } from 'node:fs'
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,10 @@
1
+ import VError from 'verror'
2
+
3
+ export class FileSystemError extends VError {
4
+ constructor(error, message) {
5
+ super(error, message)
6
+ if (error && error.code) {
7
+ this.code = error.code
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,6 @@
1
+ import * as ExensionInstall from './ExtensionInstall.js'
2
+
3
+ // prettier-ignore
4
+ export const Commands = {
5
+ 'ExtensionInstall.install': ExensionInstall.install,
6
+ }
@@ -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, extract } = await import('../Download/Download.js')
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 extract(
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 = /[a-z]+\.[a-z\-]*theme-[a-z\d\-]+$/
262
+ const RE_THEME = /theme-[a-z\d\-]+$/
262
263
  const isTheme = (extensionId) => {
263
264
  return RE_THEME.test(extensionId)
264
265
  }
@@ -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
  }
@@ -1 +1,5 @@
1
1
  export const ENOENT = 'ENOENT'
2
+
3
+ export const EXDEV = 'EXDEV'
4
+
5
+ export const EEXIST = 'EEXIST'
@@ -15,3 +15,7 @@ export const join = (...parts) => {
15
15
  export const dirname = (path) => {
16
16
  return NodePath.dirname(path)
17
17
  }
18
+
19
+ export const basename = (path) => {
20
+ return NodePath.basename(path)
21
+ }
@@ -37,6 +37,17 @@ 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
+ console.log({ argv })
45
+ if (argv.startsWith('--theme=')) {
46
+ overrides['workbench.colorTheme'] = argv.slice('--theme='.length)
47
+ }
48
+ }
49
+ return overrides
50
+ }
40
51
  // TODO compare with timestamp/hash that preferences are fresh
41
52
  export const getAll = async () => {
42
53
  try {
@@ -50,8 +61,13 @@ export const getAll = async () => {
50
61
  // }
51
62
  const defaultPreferences = await getDefaultPreferences()
52
63
  const userPreferences = await getUserPreferences()
64
+ const overrides = getOverrides()
53
65
  // TODO separate backend and frontend preferences, ui only needs frontend preferences
54
- const preferences = { ...defaultPreferences, ...userPreferences }
66
+ const preferences = {
67
+ ...defaultPreferences,
68
+ ...userPreferences,
69
+ ...overrides,
70
+ }
55
71
  // try {
56
72
  // await mkdir(dirname(CACHED_SETTINGS_PATH), { recursive: true })
57
73
  // 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
+ }
@@ -0,0 +1,15 @@
1
+ import { file, dir } from 'tmp-promise'
2
+
3
+ export const getTmpFile = async () => {
4
+ const { path } = await file({
5
+ prefix: 'lvce-extension-',
6
+ })
7
+ return path
8
+ }
9
+
10
+ export const getTmpDir = async () => {
11
+ const { path } = await dir({
12
+ prefix: 'lvce-extension-',
13
+ })
14
+ return path
15
+ }