@lvce-editor/server 0.103.20 → 0.103.22

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.
@@ -2,7 +2,7 @@ This project incorporates components from the projects listed below, that may ha
2
2
  differing from this project:
3
3
 
4
4
 
5
- 1) License Notice for static/e2306f5/icons (from https://github.com/microsoft/vscode-codicons)
5
+ 1) License Notice for static/5af6a2b/icons (from https://github.com/microsoft/vscode-codicons)
6
6
  ---------------------------------------
7
7
 
8
8
  Attribution 4.0 International
@@ -402,7 +402,7 @@ public licenses.
402
402
  Creative Commons may be contacted at creativecommons.org.
403
403
 
404
404
 
405
- 2) License Notice for static/e2306f5/fonts/FiraCode-VariableFont.ttf (from https://github.com/tonsky/FiraCode)
405
+ 2) License Notice for static/5af6a2b/fonts/FiraCode-VariableFont.ttf (from https://github.com/tonsky/FiraCode)
406
406
  ---------------------------------------
407
407
 
408
408
  Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/server",
3
- "version": "0.103.20",
3
+ "version": "0.103.22",
4
4
  "description": "Run LVCE Editor as a server.",
5
5
  "main": "index.js",
6
6
  "bin": "bin/server.js",
@@ -20,7 +20,8 @@
20
20
  "node": ">=24"
21
21
  },
22
22
  "dependencies": {
23
- "@lvce-editor/shared-process": "0.103.20",
24
- "@lvce-editor/static-server": "0.103.20"
23
+ "@lvce-editor/jsonc-parser": "^1.5.0",
24
+ "@lvce-editor/shared-process": "0.103.22",
25
+ "@lvce-editor/static-server": "0.103.22"
25
26
  }
26
27
  }
@@ -0,0 +1,78 @@
1
+ import { parse } from '@lvce-editor/jsonc-parser'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { homedir, tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+
6
+ const applicationName = 'lvce-oss'
7
+ const keyPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/
8
+ const optionsWithSeparateValues = new Set(['--connection-token', '--idle-timeout', '--link', '--only-extension', '--port', '--test-path'])
9
+
10
+ const getArgument = (key, value) => {
11
+ return `--${key}=${value}`
12
+ }
13
+
14
+ const getArgumentsForValue = (key, value) => {
15
+ if (value === true) {
16
+ return [`--${key}`]
17
+ }
18
+ if (value === false) {
19
+ return []
20
+ }
21
+ if (typeof value === 'string' || typeof value === 'number') {
22
+ return [getArgument(key, value)]
23
+ }
24
+ if (Array.isArray(value) && value.every((item) => typeof item === 'string' || typeof item === 'number')) {
25
+ return value.map((item) => getArgument(key, item))
26
+ }
27
+ throw new TypeError(`Invalid argv.json value for "${key}": expected a boolean, string, number, or array of strings and numbers`)
28
+ }
29
+
30
+ export const parseArgvConfig = (content) => {
31
+ const config = parse(content)
32
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
33
+ throw new TypeError('Invalid argv.json: expected an object')
34
+ }
35
+ const argumentsFromConfig = []
36
+ for (const [key, value] of Object.entries(config)) {
37
+ if (!keyPattern.test(key)) {
38
+ throw new TypeError(`Invalid argv.json key "${key}"`)
39
+ }
40
+ argumentsFromConfig.push(...getArgumentsForValue(key, value))
41
+ }
42
+ return argumentsFromConfig
43
+ }
44
+
45
+ export const getArgvConfigPath = (env = process.env, homeDirectory = homedir()) => {
46
+ const configDirectory = env.XDG_CONFIG_HOME || (homeDirectory ? join(homeDirectory, '.config') : tmpdir())
47
+ return join(configDirectory, applicationName, 'argv.json')
48
+ }
49
+
50
+ export const load = async (path) => {
51
+ try {
52
+ const content = await readFile(path, 'utf8')
53
+ return parseArgvConfig(content)
54
+ } catch (error) {
55
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
56
+ return []
57
+ }
58
+ throw error
59
+ }
60
+ }
61
+
62
+ export const prepend = (argv, argumentsToPrepend) => {
63
+ argv.splice(2, 0, ...argumentsToPrepend)
64
+ }
65
+
66
+ export const getWorkspaceArgument = (args) => {
67
+ for (let index = 0; index < args.length; index++) {
68
+ const argument = args[index]
69
+ if (optionsWithSeparateValues.has(argument)) {
70
+ index++
71
+ continue
72
+ }
73
+ if (!argument.startsWith('-')) {
74
+ return argument
75
+ }
76
+ }
77
+ return ''
78
+ }
@@ -2,7 +2,7 @@ const defaultIdleTimeout = 3 * 60 * 60 * 1000
2
2
 
3
3
  const getArgument = (argv, name) => {
4
4
  const prefix = `${name}=`
5
- const argument = argv.find((value) => value.startsWith(prefix))
5
+ const argument = argv.findLast((value) => value.startsWith(prefix))
6
6
  return argument ? argument.slice(prefix.length) : undefined
7
7
  }
8
8
 
package/src/server.js CHANGED
@@ -5,17 +5,19 @@ import { createServer } from 'node:http'
5
5
  import { dirname, join, resolve } from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
7
  import { Worker } from 'node:worker_threads'
8
+ import * as ArgvConfig from './argvConfig.js'
8
9
  import { getRemoteSshOptions, isAuthenticatedRemoteRequest } from './remoteSshOptions.js'
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url))
11
12
  const ROOT = resolve(__dirname, '../')
12
13
 
13
14
  const { argv, env } = process
14
-
15
- let argv2 = argv[2]
15
+ const configArguments = await ArgvConfig.load(ArgvConfig.getArgvConfigPath())
16
+ ArgvConfig.prepend(argv, configArguments)
16
17
 
17
18
  // TODO pass argv to shared process instead of using environment variables / global variables
18
19
  const argvSliced = argv.slice(2)
20
+ let argv2 = ArgvConfig.getWorkspaceArgument(argvSliced)
19
21
  for (const arg of argvSliced) {
20
22
  if (arg.startsWith('--only-extension=')) {
21
23
  process.env['ONLY_EXTENSION'] = arg.slice('--only-extension='.length)
@@ -72,7 +74,7 @@ const isStatic = (url) => {
72
74
  if (url === '/index.html' || url.startsWith('/index.html?')) {
73
75
  return true
74
76
  }
75
- if (url.startsWith('/e2306f5')) {
77
+ if (url.startsWith('/5af6a2b')) {
76
78
  return true
77
79
  }
78
80
  if (url.startsWith('/favicon.ico')) {
@@ -0,0 +1,33 @@
1
+ import assert from 'node:assert/strict'
2
+ import { join } from 'node:path'
3
+ import test from 'node:test'
4
+ import * as ArgvConfig from '../src/argvConfig.js'
5
+
6
+ test('converts object values into command line arguments', () => {
7
+ assert.deepEqual(
8
+ ArgvConfig.parseArgvConfig(`{
9
+ // Extension development paths can be repeated.
10
+ "link": ["/test/one", "/test/two"],
11
+ "disable-custom-worker-paths": true,
12
+ "public": false,
13
+ "port": 3000
14
+ }`),
15
+ ['--link=/test/one', '--link=/test/two', '--disable-custom-worker-paths', '--port=3000'],
16
+ )
17
+ })
18
+
19
+ test('prepends config arguments before explicit command line arguments', () => {
20
+ const argv = ['/usr/bin/node', '/usr/lib/lvce/server.js', '--theme=explicit']
21
+
22
+ ArgvConfig.prepend(argv, ['--link=/test/extension', '--theme=configured'])
23
+
24
+ assert.deepEqual(argv, ['/usr/bin/node', '/usr/lib/lvce/server.js', '--link=/test/extension', '--theme=configured', '--theme=explicit'])
25
+ })
26
+
27
+ test('finds the workspace while ignoring repeated link arguments', () => {
28
+ assert.equal(ArgvConfig.getWorkspaceArgument(['--link', '/test/one', '/test/workspace', '--link=/test/two']), '/test/workspace')
29
+ })
30
+
31
+ test('uses the application config directory for argv.json', () => {
32
+ assert.equal(ArgvConfig.getArgvConfigPath({ XDG_CONFIG_HOME: '/test/config' }, '/test/home'), join('/test/config', 'lvce-oss', 'argv.json'))
33
+ })
@@ -22,6 +22,22 @@ test('parses private remote SSH server options', () => {
22
22
  })
23
23
  })
24
24
 
25
+ test('uses the last value when an explicit argument follows argv.json arguments', () => {
26
+ assert.deepEqual(
27
+ getRemoteSshOptions(
28
+ ['--as-remote-ssh-server', '--port=3000', '--connection-token=config', '--port=45123', '--connection-token=explicit'],
29
+ {},
30
+ ),
31
+ {
32
+ enabled: true,
33
+ host: '127.0.0.1',
34
+ idleTimeout: 10_800_000,
35
+ port: 45123,
36
+ token: 'explicit',
37
+ },
38
+ )
39
+ })
40
+
25
41
  test('requires an authentication token in remote mode', () => {
26
42
  assert.throws(() => getRemoteSshOptions(['--as-remote-ssh-server'], {}), /requires --connection-token/)
27
43
  })
@@ -85,7 +85,10 @@ test('remote mode authenticates and exposes existing workspace processes', { ski
85
85
  const token = 'integration-secret'
86
86
  const child = spawn(process.execPath, [serverPath, '--as-remote-ssh-server', '--port=0', `--connection-token=${token}`, '--idle-timeout=30000'], {
87
87
  detached: true,
88
- env: process.env,
88
+ env: {
89
+ ...process.env,
90
+ XDG_CONFIG_HOME: directory,
91
+ },
89
92
  stdio: ['ignore', 'pipe', 'pipe'],
90
93
  })
91
94
  context.after(async () => {