-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparseScript.js
More file actions
111 lines (88 loc) · 3.58 KB
/
parseScript.js
File metadata and controls
111 lines (88 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { indentLine } from '@dr-js/core/module/common/string.js'
const COMPLEX_BASH_COMMAND_SET = new Set([ // common known complex command, will interfere with later parsing `&&`, do not parse further
'bash', 'sh', '.', 'source',
'exec', 'eval',
'su', 'sudo',
'if',
'cd', // TODO: try parse simple cd, to unwrap further
'export',
'ssh'
])
const SIMPLE_BASH_COMMAND_SET = new Set([ // common known simple command, should just direct run
'node', 'npx', 'git',
'rm', 'mkdir',
'echo', 'cat',
'exit', 'kill'
])
const REGEXP_INLINE_ENV = /^(?:env )?\w+=\S+ \S/
const REGEXP_ESCAPE = /\\/g
const REGEXP_QUOTE = /[" ]/g
const wrapJoinBashArgs = (args) => args.map((arg) => `"${arg.replace(REGEXP_ESCAPE, '\\\\').replace(REGEXP_QUOTE, '\\$&')}"`).join(' ')
const warpBashSubShell = (command) => `(
${indentLine(command, ' ')}
)`
const parseCommand = (
packageJSON,
scriptString,
level,
tabLog = (level, ...args) => {}
) => {
tabLog(level, '[parseCommand]', `input: <${scriptString}>`)
scriptString = scriptString.trim()
const [ scriptLeadingCommand, scriptSecondCommand, ...scriptExtraCommandList ] = scriptString.split(' ')
if (
COMPLEX_BASH_COMMAND_SET.has(scriptLeadingCommand) ||
scriptLeadingCommand.startsWith('./')
) {
tabLog(level, '- directly executable complex command, return')
return scriptString
} else tabLog(level, `? not directly executable complex command: ${scriptLeadingCommand}`)
if (scriptString.includes(' && ')) {
tabLog(level, '- combo command, split')
const subCommandList = scriptString.split(' && ')
return warpBashSubShell(subCommandList.map((command) => parseCommand(packageJSON, command, level + 1, tabLog) || command).join('\n'))
} else tabLog(level, '? not combo command, I guess')
if (SIMPLE_BASH_COMMAND_SET.has(scriptLeadingCommand)) {
tabLog(level, '- directly executable simple command, return')
return scriptString
} else tabLog(level, `? not directly executable simple command: ${scriptLeadingCommand}`)
if (REGEXP_INLINE_ENV.test(scriptString)) {
tabLog(level, '- env prefixing command, return')
return scriptString
} else tabLog(level, '? not env prefixing command, I guess')
// TODO: consider allow package dependency command
// TODO: consider allow package dependency command
// TODO: consider allow package dependency command
if (
scriptSecondCommand === 'run' &&
[ 'npm', 'yarn' ].includes(scriptLeadingCommand)
) {
tabLog(level, '- package script, parse')
const [ scriptName, ...extraArgs ] = scriptExtraCommandList
extraArgs[ 0 ] === '--' && extraArgs.shift() // concat argument
return parsePackageScript(packageJSON, scriptName, extraArgs.join(' '), level + 1, tabLog)
} else tabLog(level, '? unknown npm/yarn script')
tabLog(level, '? unknown script, bail')
return ''
}
const parsePackageScript = (
packageJSON,
scriptName,
extraArgsString = '',
level,
tabLog = (level, ...args) => {}
) => {
tabLog(level, '[parsePackageScript]', `script name: <${scriptName}>, extra: ${extraArgsString}`)
const scriptString = packageJSON[ 'scripts' ][ scriptName ]
if (!scriptString) throw new Error(`[parsePackageScript] missing script with name: ${scriptName}`)
const resultCommand = parseCommand(packageJSON, [ scriptString, extraArgsString ].filter(Boolean).join(' '), level + 1, tabLog)
if (resultCommand) return resultCommand
tabLog(level, '? un-parsed script, bail to npm run')
return [ `npm run "${scriptName}"`, extraArgsString ].filter(Boolean).join(' -- ')
}
export {
wrapJoinBashArgs,
warpBashSubShell,
parseCommand,
parsePackageScript
}