@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.
- package/ThirdPartyNotices.txt +2 -2
- package/package.json +4 -3
- package/src/argvConfig.js +78 -0
- package/src/remoteSshOptions.js +1 -1
- package/src/server.js +5 -3
- package/test/argvConfig.test.js +33 -0
- package/test/remoteSshOptions.test.js +16 -0
- package/test/remoteSshServer.integration.test.js +4 -1
package/ThirdPartyNotices.txt
CHANGED
|
@@ -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/
|
|
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/
|
|
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.
|
|
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/
|
|
24
|
-
"@lvce-editor/
|
|
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
|
+
}
|
package/src/remoteSshOptions.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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('/
|
|
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:
|
|
88
|
+
env: {
|
|
89
|
+
...process.env,
|
|
90
|
+
XDG_CONFIG_HOME: directory,
|
|
91
|
+
},
|
|
89
92
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
90
93
|
})
|
|
91
94
|
context.after(async () => {
|