@lvce-editor/shared-process 0.7.3 → 0.7.4

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.
@@ -697,5 +697,45 @@
697
697
  {
698
698
  "key": "ctrl+-",
699
699
  "command": "Window.zoomOut"
700
+ },
701
+ {
702
+ "key": "ArrowDown",
703
+ "command": "TitleBarMenuBar.handleKeyArrowDown",
704
+ "when": "focus.TitleBarMenuBar"
705
+ },
706
+ {
707
+ "key": "ArrowUp",
708
+ "command": "TitleBarMenuBar.handleKeyArrowUp",
709
+ "when": "focus.TitleBarMenuBar"
710
+ },
711
+ {
712
+ "key": "ArrowRight",
713
+ "command": "TitleBarMenuBar.handleKeyArrowRight",
714
+ "when": "focus.TitleBarMenuBar"
715
+ },
716
+ {
717
+ "key": "ArrowLeft",
718
+ "command": "TitleBarMenuBar.handleKeyArrowLeft",
719
+ "when": "focus.TitleBarMenuBar"
720
+ },
721
+ {
722
+ "key": "Space",
723
+ "command": "TitleBarMenuBar.handleKeySpace",
724
+ "when": "focus.TitleBarMenuBar"
725
+ },
726
+ {
727
+ "key": "Home",
728
+ "command": "TitleBarMenuBar.handleKeyHome",
729
+ "when": "focus.TitleBarMenuBar"
730
+ },
731
+ {
732
+ "key": "End",
733
+ "command": "TitleBarMenuBar.handleKeyEnd",
734
+ "when": "focus.TitleBarMenuBar"
735
+ },
736
+ {
737
+ "key": "Escape",
738
+ "command": "TitleBarMenuBar.handleKeyEscape",
739
+ "when": "focus.TitleBarMenuBar"
700
740
  }
701
741
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/shared-process",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -20,8 +20,8 @@
20
20
  "node": ">=16"
21
21
  },
22
22
  "dependencies": {
23
- "@lvce-editor/extension-host": "0.7.3",
24
- "@lvce-editor/pty-host": "0.7.3",
23
+ "@lvce-editor/extension-host": "0.7.4",
24
+ "@lvce-editor/pty-host": "0.7.4",
25
25
  "chokidar": "^3.5.3",
26
26
  "debug": "^4.3.4",
27
27
  "execa": "^6.1.0",
@@ -6,6 +6,8 @@ const getModule = (argv0) => {
6
6
  return import('../CliList/CliList.js')
7
7
  case 'link':
8
8
  return import('../CliLink/CliLink.js')
9
+ case 'unlink':
10
+ return import('../CliUnlink/CliUnlink.js')
9
11
  default:
10
12
  throw new Error('command not found')
11
13
  }
@@ -0,0 +1,6 @@
1
+ import * as ExtensionUnlink from '../ExtensionUnlink/ExtensionUnlink.js'
2
+
3
+ export const handleCliArgs = async (argv, console, process) => {
4
+ const path = argv.length > 1 ? argv[1] : process.cwd()
5
+ await ExtensionUnlink.unlink(path)
6
+ }
@@ -1,8 +1,10 @@
1
1
  import VError from 'verror'
2
+ import * as FileSystemErrorCodes from '../FileSystemErrorCodes/FileSystemErrorCodes.js'
2
3
 
3
4
  export class FileNotFoundError extends VError {
4
5
  constructor(path) {
5
6
  super(`File not found '${path}'`)
6
7
  this.name = 'FileNotFoundError'
8
+ this.code = FileSystemErrorCodes.ENOENT
7
9
  }
8
10
  }
@@ -4,7 +4,7 @@ import * as Timeout from '../Timeout/Timeout.js'
4
4
 
5
5
  // TODO maybe rename to extension host management for clarity
6
6
 
7
- const CONNECTION_TIMEOUT = 1000
7
+ const CONNECTION_TIMEOUT = 3000
8
8
 
9
9
  export const create = async (ipc, socket) => {
10
10
  Assert.object(socket)
@@ -1,4 +1,6 @@
1
1
  import VError from 'verror'
2
+ import * as ExtensionManifest from '../ExtensionManifest/ExtensionManifest.js'
3
+ import * as ExtensionManifestStatus from '../ExtensionManifestStatus/ExtensionManifestStatus.js'
2
4
  import * as FileSystem from '../FileSystem/FileSystem.js'
3
5
  import * as FileSystemErrorCodes from '../FileSystemErrorCodes/FileSystemErrorCodes.js'
4
6
  import * as Path from '../Path/Path.js'
@@ -7,29 +9,30 @@ import * as SymLink from '../SymLink/SymLink.js'
7
9
 
8
10
  const linkFallBack = async (path) => {
9
11
  try {
10
- const manifestPath = Path.join(path, 'extension.json')
11
- if (!(await FileSystem.exists(manifestPath))) {
12
- throw new Error('no extension manifest found')
12
+ const manifest = await ExtensionManifest.get(path)
13
+ if (manifest.status === ExtensionManifestStatus.Rejected) {
14
+ throw manifest.reason
13
15
  }
14
- const extensionsPath = Platform.getExtensionsPath()
15
- const baseName = Path.basename(path)
16
- const to = Path.join(extensionsPath, baseName)
16
+ const linkedExtensionsPath = Platform.getLinkedExtensionsPath()
17
+ // @ts-ignore
18
+ const to = Path.join(linkedExtensionsPath, manifest.id)
17
19
  await FileSystem.remove(to)
18
20
  await SymLink.createSymLink(path, to)
19
21
  } catch (error) {
22
+ console.log({ error })
20
23
  throw new VError(error, `Failed to link extension`)
21
24
  }
22
25
  }
23
26
 
24
27
  export const link = async (path) => {
25
28
  try {
26
- const manifestPath = Path.join(path, 'extension.json')
27
- if (!(await FileSystem.exists(manifestPath))) {
28
- throw new Error('no extension manifest found')
29
+ const manifest = await ExtensionManifest.get(path)
30
+ if (manifest.status === ExtensionManifestStatus.Rejected) {
31
+ throw manifest.reason
29
32
  }
30
- const extensionsPath = Platform.getExtensionsPath()
31
- const baseName = Path.basename(path)
32
- const to = Path.join(extensionsPath, baseName)
33
+ const linkedExtensionsPath = Platform.getLinkedExtensionsPath()
34
+ // @ts-ignore
35
+ const to = Path.join(linkedExtensionsPath, manifest.id)
33
36
  await SymLink.createSymLink(path, to)
34
37
  } catch (error) {
35
38
  if (error && error.code === FileSystemErrorCodes.EEXIST) {
@@ -1,14 +1,9 @@
1
- import { readdir, readFile } from 'node:fs/promises'
2
- import { basename, dirname, join } from 'node:path'
1
+ import { basename } from 'node:path'
3
2
  import VError from 'verror'
3
+ import * as ExtensionManifestInputType from '../ExtensionManifestInputType/ExtensionManifestInputType.js'
4
+ import * as ExtensionManifests from '../ExtensionManifests/ExtensionManifests.js'
4
5
  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
- }
6
+ import * as ExtensionManifestStatus from '../ExtensionManifestStatus/ExtensionManifestStatus.js'
12
7
 
13
8
  const getManifestVersion = (json) => {
14
9
  if (json && json.version && typeof json.version === 'string') {
@@ -17,17 +12,15 @@ const getManifestVersion = (json) => {
17
12
  return 'n/a'
18
13
  }
19
14
 
20
- const getManifestId = (json, jsonPath) => {
15
+ const getManifestId = (json) => {
21
16
  if (json && json.id && typeof json.id === 'string') {
22
17
  return json.id
23
18
  }
24
- return basename(jsonPath)
19
+ return basename(json.path)
25
20
  }
26
21
 
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)
22
+ const getManifestInfo = (json) => {
23
+ const id = getManifestId(json)
31
24
  const version = getManifestVersion(json)
32
25
  return {
33
26
  id,
@@ -36,31 +29,33 @@ const getManifestInfo = async (extensionPath) => {
36
29
  }
37
30
 
38
31
  const isFulfilled = (result) => {
39
- return result.status === 'fulfilled'
40
- }
41
-
42
- const getValue = (result) => {
43
- return result.value
32
+ return result.status === ExtensionManifestStatus.Resolved
44
33
  }
45
34
 
46
- const getManifestInfos = async (manifestPaths) => {
47
- const manifestInfos = await Promise.allSettled(
48
- manifestPaths.map(getManifestInfo)
49
- )
50
- const results = manifestInfos.filter(isFulfilled).map(getValue)
35
+ const getManifestInfos = (manifests) => {
36
+ const results = manifests.filter(isFulfilled).map(getManifestInfo)
51
37
  return results
52
38
  }
53
39
 
54
40
  export const list = async () => {
55
41
  try {
56
- const dirents = await readdir(extensionPath)
57
- const extensionPaths = dirents.map(getAbsolutePath)
58
- const infos = await getManifestInfos(extensionPaths)
42
+ const manifests = await ExtensionManifests.getAll([
43
+ {
44
+ type: ExtensionManifestInputType.Folder,
45
+ path: Platform.getLinkedExtensionsPath(),
46
+ },
47
+ {
48
+ type: ExtensionManifestInputType.Folder,
49
+ path: Platform.getExtensionsPath(),
50
+ },
51
+ {
52
+ type: ExtensionManifestInputType.Folder,
53
+ path: Platform.getBuiltinExtensionsPath(),
54
+ },
55
+ ])
56
+ const infos = getManifestInfos(manifests)
59
57
  return infos
60
58
  } catch (error) {
61
- if (error && error.code === 'ENOENT') {
62
- return []
63
- }
64
59
  throw new VError(error, `Failed to list extensions`)
65
60
  }
66
61
  }
@@ -1,14 +1,12 @@
1
- import isObject from 'is-object'
2
1
  import { mkdir, readdir, rename, rm } from 'node:fs/promises'
3
- import path from 'node:path'
4
2
  import { performance } from 'node:perf_hooks'
5
3
  import VError from 'verror'
6
4
  import * as Debug from '../Debug/Debug.js'
7
- import * as ReadJson from '../JsonFile/JsonFile.js'
5
+ import * as ExtensionManifestInputType from '../ExtensionManifestInputType/ExtensionManifestInputType.js'
6
+ import * as ExtensionManifests from '../ExtensionManifests/ExtensionManifests.js'
8
7
  import * as Path from '../Path/Path.js'
9
8
  import * as Platform from '../Platform/Platform.js'
10
9
  import * as Queue from '../Queue/Queue.js'
11
- import * as ExtensionManifestStatus from '../ExtensionManifestStatus/ExtensionManifestStatus.js'
12
10
 
13
11
  const isLanguageBasics = (extension) => {
14
12
  if (extension && extension.id) {
@@ -183,121 +181,63 @@ const getInstalledExtensionPaths = async () => {
183
181
  }
184
182
  }
185
183
 
186
- const RE_EXTENSION_FRAGMENT = /.+(\/|\\)(.+)$/
187
-
188
- const inferExtensionId = (absolutePath) => {
189
- const match = absolutePath.match(RE_EXTENSION_FRAGMENT)
190
- if (match) {
191
- return match[2]
192
- }
193
- return ''
194
- }
195
-
196
- // TODO json parsing and error handling should happen in renderer process
197
- const getExtensionManifest = async (path) => {
198
- try {
199
- const absolutePath = Path.join(path, 'extension.json')
200
- const json = await ReadJson.readJson(absolutePath)
201
- if (!isObject(json)) {
202
- // TODO should include stack of extension json file here
203
- throw new VError('Invalid manifest file: Not an JSON object.')
204
- }
205
- return {
206
- ...json,
207
- path,
208
- status: ExtensionManifestStatus.Resolved,
209
- }
210
- } catch (error) {
211
- const id = inferExtensionId(path)
212
- return {
213
- path,
214
- status: ExtensionManifestStatus.Rejected,
215
- reason: new VError(
216
- error,
217
- `Failed to load extension "${id}": Failed to load extension manifest`
218
- ),
219
- }
220
- }
221
- }
222
-
223
- /**
224
- * @param {string[]} paths
225
- */
226
- const getExtensionManifests = async (paths) => {
227
- const allResults = await Promise.all(paths.map(getExtensionManifest))
228
- return allResults
229
- }
230
-
231
184
  export const getBuiltinExtensions = async () => {
232
- const builtinExtensionPaths = await getBuiltinExtensionPaths()
233
- const builtinExtensions = await getExtensionManifests(builtinExtensionPaths)
234
- return builtinExtensions
185
+ return ExtensionManifests.getAll([
186
+ {
187
+ type: ExtensionManifestInputType.Folder,
188
+ path: Platform.getBuiltinExtensionsPath(),
189
+ },
190
+ ])
235
191
  }
236
192
 
237
- export const getInstalledExtensions = async () => {
238
- const installedExtensionPaths = await getInstalledExtensionPaths()
239
- const installedExtensions = await getExtensionManifests(
240
- installedExtensionPaths
241
- )
242
- return installedExtensions
193
+ export const getInstalledExtensions = () => {
194
+ return ExtensionManifests.getAll([
195
+ {
196
+ type: ExtensionManifestInputType.Folder,
197
+ path: Platform.getExtensionsPath(),
198
+ },
199
+ ])
243
200
  }
244
201
 
245
202
  export const getExtensions = async () => {
246
- // TODO only read from env once
247
- const builtinExtensions = await getBuiltinExtensions()
248
- const onlyExtensionPath = Platform.getOnlyExtensionPath()
249
- if (onlyExtensionPath) {
250
- const absolutePath = path.resolve(onlyExtensionPath)
251
- const [onlyExtension] = await getExtensionManifests([absolutePath])
252
- const enabledExtensions = getEnabledExtensionWithOnlyExtension(
253
- builtinExtensions,
254
- onlyExtension
255
- )
256
- return enabledExtensions
257
- }
258
- const installedExtensions = await getInstalledExtensions()
259
- return [...builtinExtensions, ...installedExtensions]
260
- }
261
-
262
- const RE_THEME = /theme-[a-z\d\-]+$/
263
- const isTheme = (extensionId) => {
264
- return RE_THEME.test(extensionId)
265
- }
266
-
267
- const getThemeExtensionsBuiltin = async () => {
268
- const builtinExtensionsPath = Platform.getBuiltinExtensionsPath()
269
- const dirents = await readdir(builtinExtensionsPath)
270
- const filteredDirents = dirents.filter(isTheme)
271
- const paths = filteredDirents.map(getAbsoluteBuiltinExtensionPath)
272
- }
273
-
274
- export const getThemeExtensions = async () => {
275
- const builtinExtensionsPath = Platform.getBuiltinExtensionsPath()
276
- const dirents = await readdir(builtinExtensionsPath)
277
- const paths = []
278
- for (const dirent of dirents) {
279
- if (isTheme(dirent)) {
280
- paths.push(getAbsoluteBuiltinExtensionPath(dirent))
281
- }
282
- }
283
- const onlyExtensionPath = Platform.getOnlyExtensionPath()
284
- if (onlyExtensionPath && isTheme(onlyExtensionPath)) {
285
- paths.push(onlyExtensionPath)
286
- }
287
- const builtinExtensions = await getExtensionManifests(paths)
288
- return builtinExtensions
203
+ return ExtensionManifests.getAll([
204
+ {
205
+ type: ExtensionManifestInputType.OnlyExtension,
206
+ path: Platform.getOnlyExtensionPath(),
207
+ },
208
+ {
209
+ type: ExtensionManifestInputType.Folder,
210
+ path: Platform.getLinkedExtensionsPath(),
211
+ },
212
+ {
213
+ type: ExtensionManifestInputType.Folder,
214
+ path: Platform.getExtensionsPath(),
215
+ },
216
+ {
217
+ type: ExtensionManifestInputType.Folder,
218
+ path: Platform.getBuiltinExtensionsPath(),
219
+ },
220
+ ])
289
221
  }
290
222
 
291
223
  export const getDisabledExtensions = async () => {
292
- const disabledExtensionPaths = await getDisabledExtensionPaths()
293
- const disabledExtensions = await getExtensionManifests(disabledExtensionPaths)
294
- return disabledExtensions
224
+ return ExtensionManifests.getAll([
225
+ {
226
+ type: ExtensionManifestInputType.Folder,
227
+ path: Platform.getDisabledExtensionsPath(),
228
+ },
229
+ ])
295
230
  }
296
231
 
297
232
  export const getAllExtensions = async () => {
298
233
  const onlyExtensionPath = Platform.getOnlyExtensionPath()
299
234
  if (onlyExtensionPath) {
300
- return getExtensionManifests([onlyExtensionPath])
235
+ return ExtensionManifests.getAll([
236
+ {
237
+ type: ExtensionManifestInputType.OnlyExtension,
238
+ path: onlyExtensionPath,
239
+ },
240
+ ])
301
241
  }
302
242
  const t1 = performance.now()
303
243
  const [builtinExtensions, installedExtensions, disabledExtensions] =
@@ -7,7 +7,7 @@ import * as ExtensionManagement from './ExtensionManagement.js'
7
7
  // TODO test this function
8
8
  // TODO very similar with getIconTheme
9
9
  export const getColorThemeJson = async (colorThemeId) => {
10
- const extensions = await ExtensionManagement.getThemeExtensions()
10
+ const extensions = await ExtensionManagement.getExtensions()
11
11
  for (const extension of extensions) {
12
12
  if (!extension.colorThemes) {
13
13
  continue
@@ -0,0 +1,51 @@
1
+ import isObject from 'is-object'
2
+ import VError from 'verror'
3
+ import * as ExtensionManifestStatus from '../ExtensionManifestStatus/ExtensionManifestStatus.js'
4
+ import * as ReadJson from '../JsonFile/JsonFile.js'
5
+ import * as Path from '../Path/Path.js'
6
+ import * as FileSystemErrorCodes from '../FileSystemErrorCodes/FileSystemErrorCodes.js'
7
+
8
+ const RE_EXTENSION_FRAGMENT = /.+(\/|\\)(.+)$/
9
+
10
+ const inferExtensionId = (absolutePath) => {
11
+ const match = absolutePath.match(RE_EXTENSION_FRAGMENT)
12
+ if (match) {
13
+ return match[2]
14
+ }
15
+ return ''
16
+ }
17
+
18
+ // TODO json parsing and error handling should happen in renderer process
19
+ export const get = async (path) => {
20
+ try {
21
+ const absolutePath = Path.join(path, 'extension.json')
22
+ const json = await ReadJson.readJson(absolutePath)
23
+ if (!isObject(json)) {
24
+ // TODO should include stack of extension json file here
25
+ throw new VError('Invalid manifest file: Not an JSON object.')
26
+ }
27
+ return {
28
+ ...json,
29
+ path,
30
+ status: ExtensionManifestStatus.Resolved,
31
+ }
32
+ } catch (error) {
33
+ const id = inferExtensionId(path)
34
+ const enhancedError = new VError(
35
+ error,
36
+ `Failed to load extension manifest for ${id}`
37
+ )
38
+ if (error.code === FileSystemErrorCodes.ENOENT) {
39
+ // @ts-ignore
40
+ enhancedError.code = FileSystemErrorCodes.E_MANIFEST_NOT_FOUND
41
+ } else {
42
+ // @ts-ignore
43
+ enhancedError.code = error.code
44
+ }
45
+ return {
46
+ path,
47
+ status: ExtensionManifestStatus.Rejected,
48
+ reason: enhancedError,
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,3 @@
1
+ export const Folder = 1
2
+
3
+ export const OnlyExtension = 2
@@ -0,0 +1,39 @@
1
+ import * as ExtensionManifestInputType from '../ExtensionManifestInputType/ExtensionManifestInputType.js'
2
+ import * as ExtensionManifestsGetFromFolder from './ExtensionManifestsFromFolder.js'
3
+ import * as ExtensionManifestsGetFromOnlyExtension from './ExtensionManifestsFromOnlyExtension.js'
4
+
5
+ const getModule = (type) => {
6
+ switch (type) {
7
+ case ExtensionManifestInputType.Folder:
8
+ return ExtensionManifestsGetFromFolder
9
+ case ExtensionManifestInputType.OnlyExtension:
10
+ return ExtensionManifestsGetFromOnlyExtension
11
+ default:
12
+ throw new Error('unsupported input type')
13
+ }
14
+ }
15
+
16
+ const get = (input) => {
17
+ const module = getModule(input.type)
18
+ return module.getExtensionManifests(input.path)
19
+ }
20
+
21
+ const deduplicateExtensions = (extensions) => {
22
+ const seen = Object.create(null)
23
+ const uniqueExtensions = []
24
+ for (const extension of extensions) {
25
+ if (extension.id in seen) {
26
+ continue
27
+ }
28
+ seen[extension.id] = true
29
+ uniqueExtensions.push(extension)
30
+ }
31
+ return uniqueExtensions
32
+ }
33
+
34
+ export const getAll = async (inputs) => {
35
+ const manifests = await Promise.all(inputs.map(get))
36
+ const flatManifests = manifests.flat(1)
37
+ const uniqueExtensions = deduplicateExtensions(flatManifests)
38
+ return uniqueExtensions
39
+ }
@@ -0,0 +1,34 @@
1
+ import VError from 'verror'
2
+ import * as ExtensionManifest from '../ExtensionManifest/ExtensionManifest.js'
3
+ import * as FileSystem from '../FileSystem/FileSystem.js'
4
+ import * as Path from '../Path/Path.js'
5
+ import * as FileSystemErrorCodes from '../FileSystemErrorCodes/FileSystemErrorCodes.js'
6
+
7
+ const toAbsolutePaths = (path, dirents) => {
8
+ const absolutePaths = []
9
+ for (const dirent of dirents) {
10
+ absolutePaths.push(Path.join(path, dirent))
11
+ }
12
+ return absolutePaths
13
+ }
14
+
15
+ export const getExtensionManifests = async (path) => {
16
+ try {
17
+ if (!path) {
18
+ return []
19
+ }
20
+ const dirents = await FileSystem.readDir(path)
21
+ const absolutePaths = toAbsolutePaths(path, dirents)
22
+ const manifests = await Promise.all(
23
+ absolutePaths.map(ExtensionManifest.get)
24
+ )
25
+ return manifests
26
+ } catch (error) {
27
+ // TODO how to make typescript happy?
28
+ // @ts-ignore
29
+ if (error.code === FileSystemErrorCodes.ENOENT) {
30
+ return []
31
+ }
32
+ throw new VError(error, 'Failed to get extension manifests')
33
+ }
34
+ }
@@ -0,0 +1,9 @@
1
+ import * as ExtensionManifest from '../ExtensionManifest/ExtensionManifest.js'
2
+
3
+ export const getExtensionManifests = async (path) => {
4
+ if (path) {
5
+ const manifest = await ExtensionManifest.get(path)
6
+ return [manifest]
7
+ }
8
+ return []
9
+ }
@@ -0,0 +1,19 @@
1
+ import VError from 'verror'
2
+ import * as FileSystem from '../FileSystem/FileSystem.js'
3
+ import * as Path from '../Path/Path.js'
4
+ import * as Platform from '../Platform/Platform.js'
5
+
6
+ export const unlink = async (path) => {
7
+ try {
8
+ const manifestPath = Path.join(path, 'extension.json')
9
+ if (!(await FileSystem.exists(manifestPath))) {
10
+ throw new Error('no extension manifest found')
11
+ }
12
+ const linkedExtensionsPath = Platform.getLinkedExtensionsPath()
13
+ const baseName = Path.basename(path)
14
+ const to = Path.join(linkedExtensionsPath, baseName)
15
+ await FileSystem.remove(to)
16
+ } catch (error) {
17
+ throw new VError(error, `Failed to unlink extension`)
18
+ }
19
+ }
@@ -177,6 +177,18 @@ export const readDirWithFileTypes = async (path) => {
177
177
  }
178
178
  }
179
179
 
180
+ export const readDir = async (path) => {
181
+ try {
182
+ const dirents = await fs.readdir(path)
183
+ return dirents
184
+ } catch (error) {
185
+ if (error && error.code === FileSystemErrorCodes.ENOENT) {
186
+ throw new FileNotFoundError(path)
187
+ }
188
+ throw new VError(error, `Failed to read directory "${path}"`)
189
+ }
190
+ }
191
+
180
192
  export const mkdir = async (path) => {
181
193
  try {
182
194
  await fs.mkdir(path, { recursive: true })
@@ -3,3 +3,7 @@ export const ENOENT = 'ENOENT'
3
3
  export const EXDEV = 'EXDEV'
4
4
 
5
5
  export const EEXIST = 'EEXIST'
6
+
7
+ export const ENOTDIR = 'ENOTDIR'
8
+
9
+ export const E_MANIFEST_NOT_FOUND = 'E_MANIFEST_NOT_FOUND'
@@ -38,6 +38,10 @@ export const getCachedExtensionsPath = () => {
38
38
  return Path.join(cacheDir, 'cached-extensions')
39
39
  }
40
40
 
41
+ export const getLinkedExtensionsPath = () => {
42
+ return Path.join(dataDir, 'linked-extensions')
43
+ }
44
+
41
45
  export const getMarketplaceUrl = () => {
42
46
  return (
43
47
  process.env.LVCE_MARKETPLACE_URL ||
@@ -1,10 +1,12 @@
1
- import { symlink } from 'node:fs/promises'
1
+ import { mkdir, symlink } from 'node:fs/promises'
2
+ import { dirname } from 'node:path'
2
3
  import { FileSystemError } from '../Error/FileSystemError.js'
3
4
 
4
5
  // TODO maybe move this to FileSystem module
5
6
 
6
7
  export const createSymLink = async (target, path) => {
7
8
  try {
9
+ await mkdir(dirname(path), { recursive: true })
8
10
  await symlink(target, path)
9
11
  } catch (error) {
10
12
  throw new FileSystemError(
@@ -45,7 +45,7 @@ const handleSigTerm = () => {
45
45
  console.info('[shared-process] sigterm')
46
46
  }
47
47
 
48
- const knownCliArgs = ['install', 'list', 'link']
48
+ const knownCliArgs = ['install', 'list', 'link', 'unlink']
49
49
 
50
50
  const main = async () => {
51
51
  const argv = process.argv.slice(2)