-
Notifications
You must be signed in to change notification settings - Fork 65
Expand file tree
/
Copy pathcmd.mts
More file actions
183 lines (170 loc) · 5.59 KB
/
Copy pathcmd.mts
File metadata and controls
183 lines (170 loc) · 5.59 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
* Command-line utilities for Socket CLI. Handles argument parsing, flag
* processing, and command formatting.
*
* Argument Handling: - Handles both long (--flag) and short (-f) formats -
* Preserves special characters and escaping - Properly quotes arguments
* containing spaces.
*
* Command Names: - commandNameFromCamel: Convert camelCase to kebab-case
* command names - commandNameFromKebab: Convert kebab-case to camelCase.
*
* Flag Processing: - cmdFlagsToString: Format arguments for display with proper
* escaping - cmdPrefixMessage: Generate command prefix message -
* stripConfigFlags: Remove --config flags from argument list - stripDebugFlags:
* Remove debug-related flags - stripHelpFlags: Remove help flags (-h, --help)
*/
import { FLAG_HELP } from '../../constants/cli.mjs'
import { camelToKebab } from '../data/strings.mts'
const helpFlags = new Set<string>()
helpFlags.add(FLAG_HELP)
helpFlags.add('-h')
export function buildFilterFlagSets(
flagsToFilter: Parameters<typeof filterFlags>[1],
): {
readonly __proto__: null
flagsToFilterSet: Set<string>
flagsWithValueSet: Set<string>
} {
// Build set of flags to filter from the provided flag objects.
const flagsToFilterSet = new Set<string>()
const flagsWithValueSet = new Set<string>()
for (const [flagName, flag] of Object.entries(flagsToFilter)) {
const longFlag = `--${camelToKebab(flagName)}`
// Special case for negated booleans.
if (flagName === 'banner' || flagName === 'spinner') {
flagsToFilterSet.add(`--no-${flagName}`)
} else {
flagsToFilterSet.add(longFlag)
}
if (flag?.shortFlag) {
flagsToFilterSet.add(`-${flag.shortFlag}`)
}
// Track flags that take values.
if (flag.type !== 'boolean') {
flagsWithValueSet.add(longFlag)
if (flag?.shortFlag) {
flagsWithValueSet.add(`-${flag.shortFlag}`)
}
}
}
return { __proto__: null, flagsToFilterSet, flagsWithValueSet }
}
/**
* Convert command arguments to a properly formatted string representation.
*/
export function cmdFlagsToString(args: string[] | readonly string[]): string {
const result = []
for (let i = 0, { length } = args; i < length; i += 1) {
const arg = args[i]?.trim()
if (arg?.startsWith('--')) {
const nextArg = i + 1 < length ? args[i + 1]?.trim() : undefined
// Check if the next item exists and is NOT another flag.
if (nextArg && !nextArg.startsWith('--') && !nextArg.startsWith('-')) {
result.push(`${arg}=${nextArg}`)
i += 1
} else {
result.push(arg)
}
} else if (arg) {
// Include non-flag arguments (commands, package names, etc.).
result.push(arg)
}
}
return result.join(' ')
}
/**
* Convert flag values to array format for processing.
*/
export function cmdFlagValueToArray(value: unknown): string[] {
if (typeof value === 'string') {
return value.trim().split(/, */).filter(Boolean)
}
if (Array.isArray(value)) {
return value.flatMap(cmdFlagValueToArray)
}
return []
}
/**
* Add command name prefix to message text.
*/
export function cmdPrefixMessage(cmdName: string, text: string): string {
const cmdPrefix = cmdName ? `${cmdName}: ` : ''
return `${cmdPrefix}${text}`
}
/**
* Filter out Socket flags from argv before passing to subcommands.
*/
export function filterFlags(
argv: readonly string[],
flagsToFilter: Record<
string,
{ shortFlag?: string | undefined; type?: string | undefined }
>,
exceptions?: string[] | undefined,
): string[] {
const filtered: string[] = []
const { flagsToFilterSet, flagsWithValueSet } =
buildFilterFlagSets(flagsToFilter)
for (let i = 0, { length } = argv; i < length; i += 1) {
const arg = argv[i]!
// Check if this flag should be kept as an exception.
if (exceptions?.includes(arg)) {
filtered.push(arg)
// Handle flags that take values.
if (flagsWithValueSet.has(arg)) {
// Include the next argument, the flag value.
i += 1
if (i < length) {
filtered.push(argv[i]!)
}
}
} else if (flagsToFilterSet.has(arg)) {
// Skip flags that take values.
if (flagsWithValueSet.has(arg)) {
// Skip the next argument, the flag value.
i += 1
}
// Skip boolean flags, no additional argument to skip.
} else if (
arg &&
Array.from(flagsWithValueSet).some(flag => arg.startsWith(`${flag}=`))
) {
// Skip --flag=value format for Socket flags unless it's an exception.
if (exceptions?.some(exc => arg.startsWith(`${exc}=`))) {
filtered.push(arg)
}
// Otherwise skip it.
} else {
filtered.push(arg)
}
}
return filtered
}
/**
* Check if argument is a help flag.
*/
export function isHelpFlag(cmdArg: string): boolean {
return helpFlags.has(cmdArg)
}
/**
* Merge Node flags into a NODE_OPTIONS value without clobbering an inherited
* one.
*
* A child process' NODE_OPTIONS env var REPLACES, does not extend, the
* parent's, so setting it to only our own flags silently drops any NODE_OPTIONS
* the user configured globally. This joins, in order, the caller's existing
* NODE_OPTIONS ahead of the flags we add so both are honoured.
*
* The value is intentionally not quoted: it is assigned directly to an env var
* not passed through a shell, and consumers that re-tokenize NODE_OPTIONS on
* whitespace (e.g. Next.js) mishandle embedded quotes.
*/
export function mergeNodeOptions(
envNodeOptions: string | undefined,
addedFlags: string[] | readonly string[],
): string {
return [envNodeOptions, cmdFlagsToString(addedFlags)]
.filter(Boolean)
.join(' ')
}