source: frontend/node_modules/launch-editor/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 6.0 KB
Line 
1/**
2 * Copyright (c) 2015-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file at
6 * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
7 *
8 * Modified by Yuxi Evan You
9 */
10
11const fs = require('fs')
12const os = require('os')
13const path = require('path')
14const colors = require('picocolors')
15const childProcess = require('child_process')
16
17const guessEditor = require('./guess')
18const getArgumentsForPosition = require('./get-args')
19
20function wrapErrorCallback (cb) {
21 return (fileName, errorMessage) => {
22 console.log()
23 console.log(
24 colors.red('Could not open ' + path.basename(fileName) + ' in the editor.')
25 )
26 if (errorMessage) {
27 if (errorMessage[errorMessage.length - 1] !== '.') {
28 errorMessage += '.'
29 }
30 console.log(
31 colors.red('The editor process exited with an error: ' + errorMessage)
32 )
33 }
34 console.log()
35 if (cb) cb(fileName, errorMessage)
36 }
37}
38
39function isTerminalEditor (editor) {
40 switch (editor) {
41 case 'vim':
42 case 'emacs':
43 case 'nano':
44 return true
45 }
46 return false
47}
48
49const positionRE = /:(\d+)(:(\d+))?$/
50function parseFile (file) {
51 // support `file://` protocol
52 if (file.startsWith('file://')) {
53 file = require('url').fileURLToPath(file)
54 }
55
56 const fileName = file.replace(positionRE, '')
57 const match = file.match(positionRE)
58 const lineNumber = match && match[1]
59 const columnNumber = match && match[3]
60 return {
61 fileName,
62 lineNumber,
63 columnNumber
64 }
65}
66
67let _childProcess = null
68
69function launchEditor (file, specifiedEditor, onErrorCallback) {
70 const parsed = parseFile(file)
71 let { fileName } = parsed
72 const { lineNumber, columnNumber } = parsed
73
74 if (!fs.existsSync(fileName)) {
75 return
76 }
77
78 if (typeof specifiedEditor === 'function') {
79 onErrorCallback = specifiedEditor
80 specifiedEditor = undefined
81 }
82
83 onErrorCallback = wrapErrorCallback(onErrorCallback)
84
85 const [editor, ...args] = guessEditor(specifiedEditor)
86 if (!editor) {
87 onErrorCallback(fileName, null)
88 return
89 }
90
91 if (
92 process.platform === 'linux' &&
93 fileName.startsWith('/mnt/') &&
94 /Microsoft/i.test(os.release())
95 ) {
96 // Assume WSL / "Bash on Ubuntu on Windows" is being used, and
97 // that the file exists on the Windows file system.
98 // `os.release()` is "4.4.0-43-Microsoft" in the current release
99 // build of WSL, see: https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364
100 // When a Windows editor is specified, interop functionality can
101 // handle the path translation, but only if a relative path is used.
102 fileName = path.relative('', fileName)
103 }
104
105 if (lineNumber) {
106 const extraArgs = getArgumentsForPosition(editor, fileName, lineNumber, columnNumber)
107 args.push.apply(args, extraArgs)
108 } else {
109 args.push(fileName)
110 }
111
112 if (_childProcess && isTerminalEditor(editor)) {
113 // There's an existing editor process already and it's attached
114 // to the terminal, so go kill it. Otherwise two separate editor
115 // instances attach to the stdin/stdout which gets confusing.
116 _childProcess.kill('SIGKILL')
117 }
118
119 if (process.platform === 'win32') {
120 // On Windows, we need to use `exec` with the `shell: true` option,
121 // and some more sanitization is required.
122
123 // However, CMD.exe on Windows is vulnerable to RCE attacks given a file name of the
124 // form "C:\Users\myusername\Downloads\& curl 172.21.93.52".
125 // `create-react-app` used a safe file name pattern to validate user-provided file names:
126 // - https://github.com/facebook/create-react-app/pull/4866
127 // - https://github.com/facebook/create-react-app/pull/5431
128 // But that's not a viable solution for this package because
129 // it's depended on by so many meta frameworks that heavily rely on
130 // special characters in file names for filesystem-based routing.
131 // We need to at least:
132 // - Support `+` because it's used in SvelteKit and Vike
133 // - Support `$` because it's used in Remix
134 // - Support `(` and `)` because they are used in Analog, SolidStart, and Vike
135 // - Support `@` because it's used in Vike
136 // - Support `[` and `]` because they are widely used for [slug]
137 // So here we choose to use `^` to escape special characters instead.
138
139 // According to https://ss64.com/nt/syntax-esc.html,
140 // we can use `^` to escape `&`, `<`, `>`, `|`, `%`, and `^`
141 // I'm not sure if we have to escape all of these, but let's do it anyway
142 function escapeCmdArgs (cmdArgs) {
143 return cmdArgs.replace(/([&|<>,;=^])/g, '^$1')
144 }
145
146 // Need to double quote the editor path in case it contains spaces;
147 // If the fileName contains spaces, we also need to double quote it in the arguments
148 // However, there's a case that it's concatenated with line number and column number
149 // which is separated by `:`. We need to double quote the whole string in this case.
150 // Also, if the string contains the escape character `^`, it needs to be quoted, too.
151 function doubleQuoteIfNeeded(str) {
152 if (str.includes('^')) {
153 // If a string includes an escaped character, not only does it need to be quoted,
154 // but the quotes need to be escaped too.
155 return `^"${str}^"`
156 } else if (str.includes(' ')) {
157 return `"${str}"`
158 }
159 return str
160 }
161 const launchCommand = [editor, ...args.map(escapeCmdArgs)]
162 .map(doubleQuoteIfNeeded)
163 .join(' ')
164
165 _childProcess = childProcess.exec(launchCommand, {
166 stdio: 'inherit',
167 shell: true
168 })
169 } else {
170 _childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
171 }
172 _childProcess.on('exit', function (errorCode) {
173 _childProcess = null
174
175 if (errorCode) {
176 onErrorCallback(fileName, '(code ' + errorCode + ')')
177 }
178 })
179
180 _childProcess.on('error', function (error) {
181 let { code, message } = error
182 if ('ENOENT' === code) {
183 message = `${message} ('${editor}' command does not exist in 'PATH')`
184 }
185 onErrorCallback(fileName, message);
186 })
187}
188
189module.exports = launchEditor
Note: See TracBrowser for help on using the repository browser.