@lvce-editor/shared-process 0.10.0 → 0.10.2

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.
@@ -14,6 +14,7 @@ export const State = {
14
14
  AfterQuery: 10,
15
15
  InsideRound: 11,
16
16
  AfterQueryWithRules: 12,
17
+ InsideRule: 13,
17
18
  }
18
19
 
19
20
  export const StateMap = {
@@ -60,7 +61,7 @@ export const TokenMap = {
60
61
  [TokenType.CurlyClose]: 'Punctuation',
61
62
  [TokenType.PropertyColon]: 'Punctuation',
62
63
  [TokenType.CssPropertySemicolon]: 'Punctuation',
63
- [TokenType.Variable]: 'Variable',
64
+ [TokenType.Variable]: 'VariableName',
64
65
  [TokenType.None]: 'None',
65
66
  [TokenType.CssPropertyValue]: 'CssPropertyValue',
66
67
  [TokenType.Unknown]: 'Unknown',
@@ -68,12 +69,12 @@ export const TokenMap = {
68
69
  [TokenType.Numeric]: 'Numeric',
69
70
  [TokenType.NewLine]: 'NewLine',
70
71
  [TokenType.Comment]: 'Comment',
71
- [TokenType.Query]: 'Query',
72
+ [TokenType.Query]: 'CssAtRule',
72
73
  [TokenType.Text]: 'Text',
73
74
  [TokenType.CssSelectorId]: 'CssSelectorId',
74
75
  }
75
76
 
76
- const RE_SELECTOR = /^[\.a-zA-Z\d\-\:>\+\~]+/
77
+ const RE_SELECTOR = /^[\.a-zA-Z\d\-\:>\+\~\_%]+/
77
78
  const RE_SELECTOR_ID = /^#\w+/
78
79
  const RE_WHITESPACE = /^ +/
79
80
  const RE_CURLY_OPEN = /^\{/
@@ -151,17 +152,19 @@ export const tokenizeLine = (line, lineState) => {
151
152
  token = TokenType.Punctuation
152
153
  state = State.InsideAttributeSelector
153
154
  } else if ((next = part.match(RE_QUERY))) {
154
- if (
155
- next[0] === '@font-face' ||
156
- next[0] === '@-ms-viewport' ||
157
- next[0] === '@-o-viewport' ||
158
- next[0] === '@viewport'
159
- ) {
160
- token = TokenType.Query
161
- state = State.AfterQueryWithRules
162
- } else {
163
- token = TokenType.Query
164
- state = State.AfterQuery
155
+ switch (next[0]) {
156
+ case '@font-face':
157
+ case '@-ms-viewport':
158
+ case '@-o-viewport':
159
+ case '@viewport':
160
+ token = TokenType.Query
161
+ state = State.AfterQueryWithRules
162
+ break
163
+ case '@keyframes':
164
+ default:
165
+ token = TokenType.Query
166
+ state = State.AfterQuery
167
+ break
165
168
  }
166
169
  } else if ((next = part.match(RE_STAR))) {
167
170
  token = TokenType.CssSelector
@@ -238,7 +241,7 @@ export const tokenizeLine = (line, lineState) => {
238
241
  case State.InsideBlockComment:
239
242
  if ((next = part.match(RE_BLOCK_COMMENT_END))) {
240
243
  token = TokenType.Comment
241
- state = State.TopLevelContent
244
+ state = stack.pop() || State.TopLevelContent
242
245
  } else if ((next = part.match(RE_BLOCK_COMMENT_CONTENT))) {
243
246
  token = TokenType.Comment
244
247
  state = State.InsideBlockComment
@@ -257,6 +260,10 @@ export const tokenizeLine = (line, lineState) => {
257
260
  if (next[0].startsWith('--')) {
258
261
  token = TokenType.Variable
259
262
  }
263
+ } else if ((next = part.match(RE_BLOCK_COMMENT_START))) {
264
+ token = TokenType.Comment
265
+ state = State.InsideBlockComment
266
+ stack.push(State.InsideSelector)
260
267
  } else if ((next = part.match(RE_ANYTHING_UNTIL_CLOSE_BRACE))) {
261
268
  token = TokenType.Unknown
262
269
  state = State.InsideSelector
@@ -85,6 +85,7 @@ const RE_COMMA = /^,/
85
85
  const RE_COLON = /^:/
86
86
  const RE_NUMERIC =
87
87
  /^((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b/
88
+ const RE_NUMERIC_OCTAL = /0(?:o|O)?[0-7][0-7_]*(n)?\b/
88
89
  const RE_LINE_COMMENT_START = /^\/\//
89
90
  const RE_LINE_COMMENT_CONTENT = /^[^\n]+/
90
91
  const RE_NEWLINE_WHITESPACE = /^\n\s*/
@@ -94,14 +95,14 @@ const RE_BLOCK_COMMENT_END = /^\*\//
94
95
  const RE_UNKNOWN_VALUE = /^[^\}\{\s,"]+/
95
96
  const RE_IMPORT = /^[a-zA-Z\.]+/
96
97
  const RE_SEMICOLON = /^;/
97
- const RE_VARIABLE_NAME = /^[a-zA-Z_][a-zA-Z\d\_]*/
98
+ const RE_VARIABLE_NAME = /^[a-zA-Z_$][a-zA-Z\d\_]*/
98
99
  const RE_LINE_COMMENT = /^\/\/[^\n]*/
99
100
  const RE_ROUND_OPEN = /^\(/
100
101
  const RE_ROUND_CLOSE = /^\)/
101
102
  const RE_DOT = /^\./
102
103
  const RE_EQUAL_SIGN = /^=/
103
104
  const RE_SINGLE_QUOTE = /^'/
104
- const RE_PUNCTUATION = /^[\(\)=\+\-><\.:;\{\}\[\]!,&\|\^\?\*]/
105
+ const RE_PUNCTUATION = /^[\(\)=\+\-><\.:;\{\}\[\]!,&\|\^\?\*%]/
105
106
  const RE_SLASH = /^\//
106
107
  const RE_ANYTHING_UNTIL_END = /^.+/s
107
108
  const RE_WHITESPACE = /^\s+/
@@ -198,7 +199,7 @@ export const tokenizeLine = (line, lineState) => {
198
199
  token = TokenType.Comment
199
200
  state = State.InsideLineComment
200
201
  } else {
201
- next = part.match(RE_PUNCTUATION)
202
+ next = part.match(RE_SLASH)
202
203
  token = TokenType.Punctuation
203
204
  state = State.TopLevelContent
204
205
  }
@@ -211,6 +212,9 @@ export const tokenizeLine = (line, lineState) => {
211
212
  } else if ((next = part.match(RE_QUOTE_BACKTICK))) {
212
213
  token = TokenType.Punctuation
213
214
  state = State.InsideBacktickString
215
+ } else if ((next = part.match(RE_NUMERIC_OCTAL))) {
216
+ token = TokenType.Numeric
217
+ state = State.TopLevelContent
214
218
  } else {
215
219
  line.slice(0, index) //?
216
220
  part //?
@@ -93,8 +93,10 @@ const RE_ANYTHING = /^.+/s
93
93
  const RE_NUMERIC =
94
94
  /^((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b/
95
95
  const RE_TEXT = /^[^\s\{\}\[\]]+/
96
- const RE_ESCAPED_QUOTE = /^\\"/
97
- const RE_BACK_SLASH = /^\\/
96
+ // const RE_ESCAPED_QUOTE = /^\\"/
97
+ // const RE_BACK_SLASH = /^\\/
98
+ const RE_STRING_ESCAPE = /^\\.?/
99
+ const RE_WORD = /^\w+/
98
100
 
99
101
  export const initialLineState = {
100
102
  state: State.TopLevelContent,
@@ -165,7 +167,6 @@ export const tokenizeLine = (line, lineState) => {
165
167
  }
166
168
  break
167
169
  case State.AfterCurlyOpen:
168
- part
169
170
  if ((next = part.match(RE_WHITESPACE))) {
170
171
  token = TokenType.Whitespace
171
172
  state = State.AfterCurlyOpen
@@ -182,6 +183,12 @@ export const tokenizeLine = (line, lineState) => {
182
183
  } else if ((next = part.match(RE_LINE_COMMENT))) {
183
184
  token = TokenType.Comment
184
185
  state = State.AfterCurlyOpen
186
+ } else if ((next = part.match(RE_WORD))) {
187
+ token = TokenType.Text
188
+ state = State.AfterPropertyName
189
+ } else if ((next = part.match(RE_ANYTHING))) {
190
+ token = TokenType.Text
191
+ state = State.AfterCurlyOpen
185
192
  } else {
186
193
  part //?
187
194
  throw new Error('no')
@@ -195,10 +202,7 @@ export const tokenizeLine = (line, lineState) => {
195
202
  token = TokenType.Punctuation
196
203
  state = stack.pop()
197
204
  state
198
- } else if ((next = part.match(RE_ESCAPED_QUOTE))) {
199
- token = TokenType.JsonPropertyName
200
- state = State.InsidePropertyNameString
201
- } else if ((next = part.match(RE_BACK_SLASH))) {
205
+ } else if ((next = part.match(RE_STRING_ESCAPE))) {
202
206
  token = TokenType.JsonPropertyName
203
207
  state = State.InsidePropertyNameString
204
208
  } else {
@@ -212,10 +216,7 @@ export const tokenizeLine = (line, lineState) => {
212
216
  } else if ((next = part.match(RE_DOUBLE_QUOTE))) {
213
217
  token = TokenType.Punctuation
214
218
  state = stack.pop()
215
- } else if ((next = part.match(RE_ESCAPED_QUOTE))) {
216
- token = TokenType.String
217
- state = State.InsideString
218
- } else if ((next = part.match(RE_BACK_SLASH))) {
219
+ } else if ((next = part.match(RE_STRING_ESCAPE))) {
219
220
  token = TokenType.String
220
221
  state = State.InsideString
221
222
  } else {
@@ -239,7 +240,6 @@ export const tokenizeLine = (line, lineState) => {
239
240
  }
240
241
  break
241
242
  case State.AfterPropertyNameAfterColon:
242
- part
243
243
  if ((next = part.match(RE_WHITESPACE))) {
244
244
  token = TokenType.Whitespace
245
245
  state = State.AfterPropertyNameAfterColon
@@ -38,6 +38,8 @@ export const TokenType = {
38
38
  String: 188,
39
39
  YamlPropertyName: 123,
40
40
  YamlPropertyValueString: 124,
41
+ Alias: 125,
42
+ Anchor: 126,
41
43
  }
42
44
 
43
45
  export const TokenMap = {
@@ -55,6 +57,8 @@ export const TokenMap = {
55
57
  [TokenType.String]: 'String',
56
58
  [TokenType.YamlPropertyName]: 'YamlPropertyName',
57
59
  [TokenType.YamlPropertyValueString]: 'YamlPropertyValueString',
60
+ [TokenType.Alias]: 'VariableName',
61
+ [TokenType.Anchor]: 'VariableName',
58
62
  }
59
63
 
60
64
  const RE_LINE_COMMENT_START = /^#/
@@ -64,7 +68,7 @@ const RE_CURLY_CLOSE = /^\}/
64
68
  const RE_PROPERTY_NAME = /^[\:@\/\\a-zA-Z\-\_\d\.]+(?=\s*:(\s+|$))/
65
69
  const RE_PROPERTY_NAME_2 =
66
70
  /^[\:@\/\\a-zA-Z\-\_\d\.\s]*[@\/\\a-zA-Z\-\_\d\.](?=\s*:(\s+|$))/
67
- const RE_PROPERTY_VALUE_1 = /^.+(?=\s+#)/s
71
+ const RE_PROPERTY_VALUE_1 = /^[^&].*(?=\s+#)/s
68
72
  const RE_SEMICOLON = /^;/
69
73
  const RE_COMMA = /^,/
70
74
  const RE_ANYTHING = /^.+/s
@@ -93,6 +97,9 @@ const RE_MULTI_LINE_STRING_START = /^(?:\||>\-|>)/
93
97
  const RE_KEY_PRE = /^\s*(\-\s*)?/
94
98
  const RE_SINGLE_QUOTE = /^'/
95
99
  const RE_STRING_SINGLE_QUOTE_CONTENT = /^[^']+/
100
+ const RE_ALIAS = /^\*.+/
101
+ const RE_ANCHOR = /^\&.+/
102
+ const RE_MERGE_KEY = /^<<\:(?=\s)/
96
103
 
97
104
  export const initialLineState = {
98
105
  state: State.TopLevelContent,
@@ -158,6 +165,9 @@ export const tokenizeLine = (line, lineState) => {
158
165
  } else if ((next = part.match(RE_SINGLE_QUOTE))) {
159
166
  token = TokenType.Punctuation
160
167
  state = State.InsidePropertyNameString
168
+ } else if ((next = part.match(RE_MERGE_KEY))) {
169
+ token = TokenType.Punctuation
170
+ state = State.AfterPropertyNameAfterColon
161
171
  } else if ((next = part.match(RE_ANYTHING))) {
162
172
  token = TokenType.Text
163
173
  state = State.TopLevelContent
@@ -204,6 +214,9 @@ export const tokenizeLine = (line, lineState) => {
204
214
  } else if ((next = part.match(RE_PROPERTY_VALUE_1))) {
205
215
  token = TokenType.YamlPropertyValueString
206
216
  state = State.AfterPropertyValue
217
+ } else if ((next = part.match(RE_ANCHOR))) {
218
+ token = TokenType.Anchor
219
+ state = State.AfterPropertyNameAfterColon
207
220
  } else if ((next = part.match(RE_ANYTHING))) {
208
221
  token = TokenType.YamlPropertyValueString
209
222
  state = State.TopLevelContent
@@ -247,6 +260,9 @@ export const tokenizeLine = (line, lineState) => {
247
260
  } else if ((next = part.match(RE_MULTI_LINE_STRING_START))) {
248
261
  token = TokenType.Punctuation
249
262
  state = State.AfterPipe
263
+ } else if ((next = part.match(RE_ALIAS))) {
264
+ token = TokenType.Alias
265
+ state = State.TopLevelContent
250
266
  } else if ((next = part.match(RE_ANYTHING))) {
251
267
  token = TokenType.YamlPropertyValueString
252
268
  state = State.TopLevelContent
@@ -309,6 +325,9 @@ export const tokenizeLine = (line, lineState) => {
309
325
  } else if ((next = part.match(RE_DASH))) {
310
326
  token = TokenType.Punctuation
311
327
  state = State.AfterDash
328
+ } else if ((next = part.match(RE_LINE_COMMENT_START))) {
329
+ token = TokenType.Comment
330
+ state = State.InsideLineComment
312
331
  } else if ((next = part.match(RE_ANYTHING))) {
313
332
  token = TokenType.Text
314
333
  state = State.TopLevelContent
@@ -85,6 +85,10 @@
85
85
  "name": "CssSelector",
86
86
  "foreground": "#BEB558"
87
87
  },
88
+ {
89
+ "name": "CssSelectorId",
90
+ "foreground": "#BE7C81"
91
+ },
88
92
  {
89
93
  "name": "CssPropertyValue",
90
94
  "foreground": "#C0C0C3"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lvce-editor/shared-process",
3
- "version": "0.10.0",
4
- "description": "",
3
+ "version": "0.10.2",
4
+ "description": "Utility package for @lvce-editor/server",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -20,8 +20,8 @@
20
20
  "node": ">=16"
21
21
  },
22
22
  "dependencies": {
23
- "@lvce-editor/extension-host": "0.10.0",
24
- "@lvce-editor/pty-host": "0.10.0",
23
+ "@lvce-editor/extension-host": "0.10.2",
24
+ "@lvce-editor/pty-host": "0.10.2",
25
25
  "debug": "^4.3.4",
26
26
  "execa": "^6.1.0",
27
27
  "exit-hook": "^3.1.0",
@@ -1,61 +1,102 @@
1
- import fs, { cpSync, readdirSync, readFileSync, writeFileSync } from 'fs'
2
- import path, { join } from 'path'
1
+ import * as FileSystem from '../FileSystem/FileSystem.js'
2
+ import * as JsonFile from '../JsonFile/JsonFile.js'
3
+ import * as Path from '../Path/Path.js'
3
4
 
4
5
  // TODO
5
6
  // - implement this for syntax highlighting projects
6
7
  // - implement this for language features web extensions
7
8
 
8
- const readExtensionManifest = (path) => {
9
- const content = readFileSync(path, 'utf8')
10
- return { ...JSON.parse(content), path }
9
+ const readExtensionManifest = async (path) => {
10
+ const json = await JsonFile.readJson(path)
11
+ return { ...json, path }
12
+ }
13
+
14
+ const getThemeName = (dirent) => {
15
+ return dirent.slice('builtin.theme-'.length)
16
+ }
17
+
18
+ const replace = async (path, occurrence, replacement) => {
19
+ const oldContent = await FileSystem.readFile(path)
20
+ if (!oldContent.includes(occurrence)) {
21
+ throw new Error(`failed to replace occurrence ${occurrence}: Not found`)
22
+ }
23
+ // @ts-ignore
24
+ const newContent = oldContent.replaceAll(occurrence, replacement)
25
+ await FileSystem.writeFile(path, newContent)
11
26
  }
12
27
 
13
28
  /**
14
- *
15
- * @param {{root:string, pathPrefix:string }} param0
29
+ * @param {string} dirent
16
30
  */
17
- export const exportStatic = async ({ root, pathPrefix }) => {
18
- const dirents = readdirSync(
19
- join(root, 'node_modules', '@lvce-editor', 'server', 'static')
20
- )
21
- const RE_COMMIT_HASH = /^[a-z\d]+$/
22
- const isCommitHash = (dirent) => {
23
- return dirent.length === 7 && dirent.match(RE_COMMIT_HASH)
24
- }
25
- const commitHash = dirents.find(isCommitHash) || ''
31
+ const isLanguageBasics = (dirent) => {
32
+ return dirent.startsWith('builtin.language-basics')
33
+ }
26
34
 
27
- const extensionJson = readExtensionManifest(join(root, 'extension.json'))
28
- const name = extensionJson.id.slice('builtin.theme-'.length)
29
- fs.rmSync(join(root, 'dist'), { recursive: true, force: true })
35
+ /**
36
+ * @param {string} dirent
37
+ */
38
+ const isTheme = (dirent) => {
39
+ return dirent.startsWith('builtin.theme-')
40
+ }
30
41
 
31
- fs.mkdirSync(path.join(root, 'dist'))
42
+ /**
43
+ * @param {string} dirent
44
+ */
45
+ const isIconTheme = (dirent) => {
46
+ return dirent === 'builtin.vscode-icons'
47
+ }
32
48
 
33
- fs.mkdirSync(
34
- join(root, 'dist', commitHash, 'extensions', `builtin.theme-${name}`),
35
- {
36
- recursive: true,
37
- }
38
- )
39
- fs.cpSync(
40
- join(root, 'node_modules', '@lvce-editor', 'server', 'static'),
41
- join(root, 'dist'),
42
- {
43
- recursive: true,
44
- }
45
- )
49
+ const compare = (a, b) => {
50
+ return a.localeCompare(b)
51
+ }
52
+
53
+ const toSorted = (objects, compare) => {
54
+ return [...objects].sort(compare)
55
+ }
46
56
 
47
- const replaceSync = (path, occurrence, replacement) => {
48
- const oldContent = readFileSync(path, 'utf8')
49
- if (!oldContent.includes(occurrence)) {
50
- throw new Error(`failed to replace occurrence ${occurrence}: Not found`)
57
+ const mergeThemes = (builtinThemes, extensionThemes) => {
58
+ const seen = []
59
+ const merged = []
60
+ for (const extensionTheme of extensionThemes) {
61
+ seen.push(extensionTheme)
62
+ merged.push(extensionTheme)
63
+ }
64
+ for (const builtinTheme of builtinThemes) {
65
+ if (seen.includes(builtinTheme)) {
66
+ continue
51
67
  }
52
- // @ts-ignore
53
- const newContent = oldContent.replaceAll(occurrence, replacement)
54
- writeFileSync(path, newContent)
68
+ merged.push(builtinTheme)
55
69
  }
70
+ const sorted = toSorted(merged, compare)
71
+ return sorted
72
+ }
73
+
74
+ const RE_COMMIT_HASH = /^[a-z\d]+$/
75
+ const isCommitHash = (dirent) => {
76
+ return dirent.length === 7 && dirent.match(RE_COMMIT_HASH)
77
+ }
78
+
79
+ /**
80
+ * @param {string} root
81
+ */
82
+ const clean = async (root) => {
83
+ await FileSystem.remove(Path.join(root, 'dist'))
84
+ await FileSystem.mkdir(Path.join(root, 'dist'))
85
+ }
86
+
87
+ /**
88
+ * @param {string} root
89
+ */
90
+ const copyStaticFiles = async (root, commitHash) => {
91
+ await FileSystem.copy(
92
+ Path.join(root, 'node_modules', '@lvce-editor', 'server', 'static'),
93
+ Path.join(root, 'dist')
94
+ )
95
+ }
56
96
 
57
- replaceSync(
58
- join(
97
+ const applyOverrides = async ({ root, commitHash, pathPrefix }) => {
98
+ await replace(
99
+ Path.join(
59
100
  root,
60
101
  'dist',
61
102
  commitHash,
@@ -67,8 +108,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
67
108
  'platform = getPlatform();',
68
109
  'platform = "web"'
69
110
  )
70
- replaceSync(
71
- join(
111
+ await replace(
112
+ Path.join(
72
113
  root,
73
114
  'dist',
74
115
  commitHash,
@@ -80,8 +121,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
80
121
  `return "/${commitHash}";`,
81
122
  `return "${pathPrefix}/${commitHash}";`
82
123
  )
83
- replaceSync(
84
- join(
124
+ await replace(
125
+ Path.join(
85
126
  root,
86
127
  'dist',
87
128
  commitHash,
@@ -93,8 +134,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
93
134
  'platform = getPlatform();',
94
135
  'platform = "web"'
95
136
  )
96
- replaceSync(
97
- join(
137
+ await replace(
138
+ Path.join(
98
139
  root,
99
140
  'dist',
100
141
  commitHash,
@@ -106,8 +147,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
106
147
  `platform2 = "remote";`,
107
148
  'platform2 = "web";'
108
149
  )
109
- replaceSync(
110
- join(
150
+ await replace(
151
+ Path.join(
111
152
  root,
112
153
  'dist',
113
154
  commitHash,
@@ -119,8 +160,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
119
160
  `return "/${commitHash}";`,
120
161
  `return "${pathPrefix}/${commitHash}";`
121
162
  )
122
- replaceSync(
123
- join(
163
+ await replace(
164
+ Path.join(
124
165
  root,
125
166
  'dist',
126
167
  commitHash,
@@ -137,8 +178,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
137
178
  return \`\${assetDir}/themes/\${colorThemeId}.json\`
138
179
  }`
139
180
  )
140
- replaceSync(
141
- join(
181
+ await replace(
182
+ Path.join(
142
183
  root,
143
184
  'dist',
144
185
  commitHash,
@@ -155,8 +196,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
155
196
  return \`\${assetDir}/icon-themes/\${iconThemeId}.json\`
156
197
  }`
157
198
  )
158
- replaceSync(
159
- join(
199
+ await replace(
200
+ Path.join(
160
201
  root,
161
202
  'dist',
162
203
  commitHash,
@@ -170,8 +211,8 @@ export const exportStatic = async ({ root, pathPrefix }) => {
170
211
  )
171
212
 
172
213
  // workaround for firefox bug
173
- replaceSync(
174
- join(
214
+ await replace(
215
+ Path.join(
175
216
  root,
176
217
  'dist',
177
218
  commitHash,
@@ -184,44 +225,43 @@ export const exportStatic = async ({ root, pathPrefix }) => {
184
225
  `export {}
185
226
  //# sourceMappingURL`
186
227
  )
187
- replaceSync(
188
- join(root, 'dist', commitHash, 'config', 'defaultSettings.json'),
189
- `"workbench.colorTheme": "slime"`,
190
- `"workbench.colorTheme": "${name}"`
191
- )
192
- replaceSync(
193
- join(root, 'dist', commitHash, 'config', 'defaultSettings.json'),
228
+
229
+ await replace(
230
+ Path.join(root, 'dist', commitHash, 'config', 'defaultSettings.json'),
194
231
  `"workbench.saveStateOnVisibilityChange": false`,
195
232
  `"workbench.saveStateOnVisibilityChange": true`
196
233
  )
197
234
 
198
- const extensionDirents = readdirSync(
199
- join(root, 'node_modules', '@lvce-editor', 'shared-process', 'extensions')
235
+ const extensionDirents = await FileSystem.readDir(
236
+ Path.join(
237
+ root,
238
+ 'node_modules',
239
+ '@lvce-editor',
240
+ 'shared-process',
241
+ 'extensions'
242
+ )
200
243
  )
201
244
 
202
- const isLanguageBasics = (dirent) => {
203
- return dirent.startsWith('builtin.language-basics')
204
- }
205
-
206
- const isTheme = (dirent) => {
207
- return dirent.startsWith('builtin.theme-')
208
- }
209
-
210
- const isIconTheme = (dirent) => {
211
- return dirent === 'builtin.vscode-icons'
212
- }
213
-
214
245
  const languageBasicsDirents = extensionDirents.filter(isLanguageBasics)
215
246
  const themeDirents = extensionDirents.filter(isTheme)
216
247
  const iconThemeDirents = extensionDirents.filter(isIconTheme)
217
248
 
218
- const writeJson = (path, json) => {
219
- const content = JSON.stringify(json, null, 2) + '\n'
220
- writeFileSync(path, content)
249
+ const getLanguages = (extension) => {
250
+ const languages = []
251
+ for (const language of extension.languages || []) {
252
+ languages.push({
253
+ ...language,
254
+ tokenize: `${pathPrefix}/${commitHash}/extensions/${extension.id}/${language.tokenize}`,
255
+ })
256
+ }
257
+ return languages
221
258
  }
222
259
 
260
+ /**
261
+ * @param {string} dirent
262
+ */
223
263
  const getManifestPath = (dirent) => {
224
- return join(
264
+ return Path.join(
225
265
  root,
226
266
  'node_modules',
227
267
  '@lvce-editor',
@@ -231,28 +271,18 @@ export const exportStatic = async ({ root, pathPrefix }) => {
231
271
  'extension.json'
232
272
  )
233
273
  }
234
- const getLanguages = (extension) => {
235
- const languages = []
236
- for (const language of extension.languages || []) {
237
- languages.push({
238
- ...language,
239
- tokenize: `${pathPrefix}/${commitHash}/extensions/${extension.id}/${language.tokenize}`,
240
- })
241
- }
242
- return languages
243
- }
244
- const languages = languageBasicsDirents
245
- .map(getManifestPath)
246
- .map(readExtensionManifest)
247
- .flatMap(getLanguages)
248
- writeJson(
249
- join(root, 'dist', commitHash, 'config', 'languages.json'),
274
+
275
+ const manifestPaths = languageBasicsDirents.map(getManifestPath)
276
+ const manifests = await Promise.all(manifestPaths.map(readExtensionManifest))
277
+ const languages = manifests.flatMap(getLanguages)
278
+ await JsonFile.writeJson(
279
+ Path.join(root, 'dist', commitHash, 'config', 'languages.json'),
250
280
  languages
251
281
  )
252
282
 
253
283
  for (const languageBasicsDirent of languageBasicsDirents) {
254
- cpSync(
255
- join(
284
+ await FileSystem.copy(
285
+ Path.join(
256
286
  root,
257
287
  'node_modules',
258
288
  '@lvce-editor',
@@ -260,21 +290,14 @@ export const exportStatic = async ({ root, pathPrefix }) => {
260
290
  'extensions',
261
291
  languageBasicsDirent
262
292
  ),
263
- join(root, 'dist', commitHash, 'extensions', languageBasicsDirent),
264
- {
265
- recursive: true,
266
- }
293
+ Path.join(root, 'dist', commitHash, 'extensions', languageBasicsDirent)
267
294
  )
268
295
  }
269
296
 
270
- const getThemeName = (dirent) => {
271
- return dirent.slice('builtin.theme-'.length)
272
- }
273
-
274
297
  for (const themeDirent of themeDirents) {
275
298
  const themeId = getThemeName(themeDirent)
276
- cpSync(
277
- join(
299
+ await FileSystem.copy(
300
+ Path.join(
278
301
  root,
279
302
  'node_modules',
280
303
  '@lvce-editor',
@@ -283,42 +306,20 @@ export const exportStatic = async ({ root, pathPrefix }) => {
283
306
  themeDirent,
284
307
  'color-theme.json'
285
308
  ),
286
- join(root, 'dist', commitHash, 'themes', `${themeId}.json`)
309
+ Path.join(root, 'dist', commitHash, 'themes', `${themeId}.json`)
287
310
  )
288
311
  }
289
312
 
290
- const compare = (a, b) => {
291
- return a.localeCompare(b)
292
- }
293
-
294
- const toSorted = (objects, compare) => {
295
- return [...objects].sort(compare)
296
- }
297
-
298
- const mergeThemes = (builtinThemes, extensionThemes) => {
299
- const seen = []
300
- const merged = []
301
- for (const extensionTheme of extensionThemes) {
302
- seen.push(extensionTheme)
303
- merged.push(extensionTheme)
304
- }
305
- for (const builtinTheme of builtinThemes) {
306
- if (seen.includes(builtinTheme)) {
307
- continue
308
- }
309
- merged.push(builtinTheme)
310
- }
311
- const sorted = toSorted(merged, compare)
312
- return sorted
313
- }
314
-
315
- const themeIds = mergeThemes(themeDirents.map(getThemeName), [name])
316
- writeJson(join(root, 'dist', commitHash, 'config', 'themes.json'), themeIds)
313
+ const themeIds = mergeThemes(themeDirents.map(getThemeName), [])
314
+ await JsonFile.writeJson(
315
+ Path.join(root, 'dist', commitHash, 'config', 'themes.json'),
316
+ themeIds
317
+ )
317
318
 
318
319
  for (const iconThemeDirent of iconThemeDirents) {
319
320
  const iconThemeId = iconThemeDirent.slice('builtin.'.length)
320
- cpSync(
321
- join(
321
+ await FileSystem.copy(
322
+ Path.join(
322
323
  root,
323
324
  'node_modules',
324
325
  '@lvce-editor',
@@ -327,10 +328,10 @@ export const exportStatic = async ({ root, pathPrefix }) => {
327
328
  iconThemeDirent,
328
329
  'icon-theme.json'
329
330
  ),
330
- join(root, 'dist', commitHash, 'icon-themes', `${iconThemeId}.json`)
331
+ Path.join(root, 'dist', commitHash, 'icon-themes', `${iconThemeId}.json`)
331
332
  )
332
- cpSync(
333
- join(
333
+ await FileSystem.copy(
334
+ Path.join(
334
335
  root,
335
336
  'node_modules',
336
337
  '@lvce-editor',
@@ -339,76 +340,280 @@ export const exportStatic = async ({ root, pathPrefix }) => {
339
340
  iconThemeDirent,
340
341
  'icons'
341
342
  ),
342
- join(root, 'dist', commitHash, 'file-icons'),
343
- {
344
- recursive: true,
345
- }
343
+ Path.join(root, 'dist', commitHash, 'file-icons')
346
344
  )
347
345
  }
348
-
349
- cpSync(
350
- join(root, 'color-theme.json'),
351
- join(root, 'dist', commitHash, 'themes', `${name}.json`)
352
- )
353
- replaceSync(
354
- join(root, 'dist', 'index.html'),
346
+ await replace(
347
+ Path.join(root, 'dist', 'index.html'),
355
348
  `/${commitHash}`,
356
349
  `${pathPrefix}/${commitHash}`
357
350
  )
358
- replaceSync(
359
- join(root, 'dist', 'index.html'),
351
+ await replace(
352
+ Path.join(root, 'dist', 'index.html'),
360
353
  `/manifest.json`,
361
354
  `${pathPrefix}/manifest.json`
362
355
  )
363
- replaceSync(
364
- join(root, 'dist', 'index.html'),
356
+
357
+ await replace(
358
+ Path.join(root, 'dist', 'index.html'),
365
359
  '</title>',
366
360
  `</title>
367
- <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">`
361
+ <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">`
368
362
  )
369
- replaceSync(
370
- join(root, 'dist', 'manifest.json'),
363
+ await replace(
364
+ Path.join(root, 'dist', 'manifest.json'),
371
365
  `/${commitHash}`,
372
366
  `${pathPrefix}/${commitHash}`
373
367
  )
374
368
 
375
- fs.copyFileSync(
376
- join(root, 'README.md'),
377
- join(
378
- root,
379
- 'dist',
380
- commitHash,
381
- 'extensions',
382
- `builtin.theme-${name}`,
383
- 'README.md'
369
+ await replace(
370
+ Path.join(root, 'dist', commitHash, 'css', 'App.css'),
371
+ `/${commitHash}`,
372
+ `${pathPrefix}/${commitHash}`
373
+ )
374
+ }
375
+
376
+ const addExtensionSeo = async ({ root, name, description }) => {
377
+ await replace(
378
+ Path.join(root, 'dist', 'index.html'),
379
+ '<title>Code Editor</title>',
380
+ `<title>${name}</title>`
381
+ )
382
+ await replace(
383
+ Path.join(root, 'dist', 'manifest.json'),
384
+ `"name": "Code Editor Web - OSS"`,
385
+ `"name": "${name}"`
386
+ )
387
+ await replace(
388
+ Path.join(root, 'dist', 'manifest.json'),
389
+ `"short_name": "Web - OSS"`,
390
+ `"short_name": "${name}"`
391
+ )
392
+ await replace(
393
+ Path.join(root, 'dist', 'manifest.json'),
394
+ `"description": "Web Code Editor."`,
395
+ `"description": "${description}"`
396
+ )
397
+ await replace(
398
+ Path.join(root, 'dist', 'index.html'),
399
+ '<meta name="description" content="Online Code Editor" />',
400
+ `<meta name="description" content="${description}" />`
401
+ )
402
+ }
403
+
404
+ const getId = (object) => {
405
+ return object.id
406
+ }
407
+
408
+ const addExtensionThemes = async ({ root, extensionJson, commitHash }) => {
409
+ const colorThemes = extensionJson.colorThemes || []
410
+ if (colorThemes.length === 0) {
411
+ return
412
+ }
413
+ for (const colorTheme of colorThemes) {
414
+ const { id, path } = colorTheme
415
+ await FileSystem.mkdir(
416
+ Path.join(root, 'dist', commitHash, 'extensions', `builtin.theme-${id}`)
417
+ )
418
+ await FileSystem.copy(
419
+ Path.join(root, path),
420
+ Path.join(root, 'dist', commitHash, 'themes', `${id}.json`)
421
+ )
422
+ await replace(
423
+ Path.join(root, 'dist', commitHash, 'config', 'defaultSettings.json'),
424
+ `"workbench.colorTheme": "slime"`,
425
+ `"workbench.colorTheme": "${id}"`
426
+ )
427
+ await FileSystem.copyFile(
428
+ Path.join(root, 'README.md'),
429
+ Path.join(
430
+ root,
431
+ 'dist',
432
+ commitHash,
433
+ 'extensions',
434
+ `builtin.theme-${id}`,
435
+ 'README.md'
436
+ )
437
+ )
438
+ await FileSystem.copyFile(
439
+ Path.join(root, 'extension.json'),
440
+ Path.join(
441
+ root,
442
+ 'dist',
443
+ commitHash,
444
+ 'extensions',
445
+ `builtin.theme-${id}`,
446
+ 'extension.json'
447
+ )
448
+ )
449
+ await FileSystem.copyFile(
450
+ Path.join(root, 'color-theme.json'),
451
+ Path.join(
452
+ root,
453
+ 'dist',
454
+ commitHash,
455
+ 'extensions',
456
+ `builtin.theme-${id}`,
457
+ 'color-theme.json'
458
+ )
384
459
  )
460
+ }
461
+ const themesJson = await JsonFile.readJson(
462
+ Path.join(root, 'dist', commitHash, 'config', 'themes.json')
463
+ )
464
+ const ids = colorThemes.map(getId)
465
+ const mergedThemes = mergeThemes(themesJson, ids)
466
+ await JsonFile.writeJson(
467
+ Path.join(root, 'dist', commitHash, 'config', 'themes.json'),
468
+ mergedThemes
469
+ )
470
+ }
471
+
472
+ const compareId = (a, b) => {
473
+ return a.id.localeCompare(b.id)
474
+ }
475
+
476
+ const mergeLanguages = (languages, extensionLanguages) => {
477
+ const seen = []
478
+ const merged = []
479
+ for (const language of extensionLanguages) {
480
+ seen.push(language.id)
481
+ merged.push(language)
482
+ }
483
+ for (const language of languages) {
484
+ if (seen.includes(language.id)) {
485
+ continue
486
+ }
487
+ merged.push(language)
488
+ }
489
+ merged.sort(compareId)
490
+ return merged
491
+ }
492
+
493
+ const toPlaygroundFile = (file) => {
494
+ return `/playground/${file}`
495
+ }
496
+
497
+ const addExtensionLanguages = async ({
498
+ root,
499
+ extensionJson,
500
+ commitHash,
501
+ pathPrefix,
502
+ }) => {
503
+ const languages = extensionJson.languages || []
504
+ if (languages.length === 0) {
505
+ return
506
+ }
507
+ const extensionId = extensionJson.id
508
+ await FileSystem.remove(
509
+ Path.join(root, 'dist', commitHash, 'extensions', extensionId)
510
+ )
511
+ await FileSystem.mkdir(
512
+ Path.join(root, 'dist', commitHash, 'extensions', extensionId)
513
+ )
514
+ await FileSystem.copyFile(
515
+ Path.join(root, 'README.md'),
516
+ Path.join(root, 'dist', commitHash, 'extensions', extensionId, 'README.md')
385
517
  )
386
- fs.copyFileSync(
387
- join(root, 'extension.json'),
388
- join(
518
+ await FileSystem.copy(
519
+ Path.join(root, 'src'),
520
+ Path.join(root, 'dist', commitHash, 'extensions', extensionId, 'src')
521
+ )
522
+ await FileSystem.copyFile(
523
+ Path.join(root, 'extension.json'),
524
+ Path.join(
389
525
  root,
390
526
  'dist',
391
527
  commitHash,
392
528
  'extensions',
393
- `builtin.theme-${name}`,
529
+ extensionId,
394
530
  'extension.json'
395
531
  )
396
532
  )
397
- fs.copyFileSync(
398
- join(root, 'color-theme.json'),
399
- join(
400
- root,
401
- 'dist',
402
- commitHash,
403
- 'extensions',
404
- `builtin.theme-${name}`,
405
- 'color-theme.json'
406
- )
533
+ const extensionLanguages = []
534
+ for (const language of languages) {
535
+ extensionLanguages.push({
536
+ ...language,
537
+ tokenize: `${pathPrefix}/${commitHash}/extensions/${extensionId}/${language.tokenize}`,
538
+ })
539
+ }
540
+ const builtinLanguages = await JsonFile.readJson(
541
+ Path.join(root, 'dist', commitHash, 'config', 'languages.json')
542
+ )
543
+ const mergedLanguages = mergeLanguages(builtinLanguages, extensionLanguages)
544
+ await JsonFile.writeJson(
545
+ Path.join(root, 'dist', commitHash, 'config', 'languages.json'),
546
+ mergedLanguages
547
+ )
548
+ await FileSystem.remove(Path.join(root, 'dist', commitHash, 'playground'))
549
+ await FileSystem.copy(
550
+ Path.join(root, 'test', 'cases'),
551
+ Path.join(root, 'dist', commitHash, 'playground')
407
552
  )
408
553
 
409
- replaceSync(
410
- join(root, 'dist', commitHash, 'css', 'App.css'),
411
- `/${commitHash}`,
412
- `${pathPrefix}/${commitHash}`
554
+ const testFiles = await FileSystem.readDir(Path.join(root, 'test', 'cases'))
555
+ const fileMap = testFiles.map(toPlaygroundFile)
556
+ await JsonFile.writeJson(
557
+ Path.join(root, 'dist', commitHash, 'config', 'fileMap.json'),
558
+ fileMap
559
+ )
560
+ }
561
+
562
+ const addExtension = async ({ root, commitHash, pathPrefix }) => {
563
+ const extensionJson = await readExtensionManifest(
564
+ Path.join(root, 'extension.json')
565
+ )
566
+ const name = extensionJson.name || extensionJson.id || ''
567
+ const description = extensionJson.description || ''
568
+ await addExtensionSeo({
569
+ root,
570
+ name,
571
+ description,
572
+ })
573
+ await addExtensionThemes({
574
+ root,
575
+ commitHash,
576
+ extensionJson,
577
+ })
578
+ await addExtensionLanguages({
579
+ root,
580
+ commitHash,
581
+ extensionJson,
582
+ pathPrefix,
583
+ })
584
+ }
585
+
586
+ /**
587
+ *
588
+ * @param {{root:string, pathPrefix:string }} param0
589
+ */
590
+ export const exportStatic = async ({ root, pathPrefix }) => {
591
+ const dirents = await FileSystem.readDir(
592
+ Path.join(root, 'node_modules', '@lvce-editor', 'server', 'static')
413
593
  )
594
+ const commitHash = dirents.find(isCommitHash) || ''
595
+
596
+ console.time('clean')
597
+ await clean(root)
598
+ console.timeEnd('clean')
599
+
600
+ console.time('copyStaticFiles')
601
+ await copyStaticFiles(root, commitHash)
602
+ console.timeEnd('copyStaticFiles')
603
+
604
+ console.time('applyOverrides')
605
+ await applyOverrides({
606
+ root,
607
+ commitHash,
608
+ pathPrefix,
609
+ })
610
+ console.timeEnd('applyOverrides')
611
+
612
+ console.time('addExtension')
613
+ await addExtension({
614
+ root,
615
+ commitHash,
616
+ pathPrefix,
617
+ })
618
+ console.timeEnd('addExtension')
414
619
  }
@@ -270,6 +270,21 @@ export const stat = async (path) => {
270
270
  export const chmod = async (path, permissions) => {
271
271
  await fs.chmod(path, permissions)
272
272
  }
273
+ export const copyFile = async (from, to) => {
274
+ try {
275
+ await fs.copyFile(from, to)
276
+ } catch (error) {
277
+ throw new VError(error, `Failed to copy file from ${from} to ${to}`)
278
+ }
279
+ }
280
+
281
+ export const cp = async (from, to) => {
282
+ try {
283
+ await fs.cp(from, to, { recursive: true })
284
+ } catch (error) {
285
+ throw new VError(error, `Failed to copy folder from ${from} to ${to}`)
286
+ }
287
+ }
273
288
 
274
289
  // export const unwatch = (id) => {
275
290
  // state.watchers[id].close()