1#!/usr/bin/env node 2import { foregroundChild } from 'foreground-child'; 3import { existsSync } from 'fs'; 4import { readFile } from 'fs/promises'; 5import { jack } from 'jackspeak'; 6import { join } from 'path'; 7import { fileURLToPath } from 'url'; 8import { globStream } from './index.js'; 9/* c8 ignore start */ 10const { version } = JSON.parse(await readFile(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8').catch(() => readFile(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'))); 11/* c8 ignore stop */ 12const j = jack({ 13 usage: 'glob [options] [<pattern> [<pattern> ...]]', 14}) 15 .description(` 16 Glob v${version} 17 18 Expand the positional glob expression arguments into any matching file 19 system paths found. 20 `) 21 .opt({ 22 cmd: { 23 short: 'c', 24 hint: 'command', 25 description: `Run the command provided, passing the glob expression 26 matches as arguments.`, 27 }, 28}) 29 .opt({ 30 default: { 31 short: 'p', 32 hint: 'pattern', 33 description: `If no positional arguments are provided, glob will use 34 this pattern`, 35 }, 36}) 37 .flag({ 38 all: { 39 short: 'A', 40 description: `By default, the glob cli command will not expand any 41 arguments that are an exact match to a file on disk. 42 43 This prevents double-expanding, in case the shell expands 44 an argument whose filename is a glob expression. 45 46 For example, if 'app/*.ts' would match 'app/[id].ts', then 47 on Windows powershell or cmd.exe, 'glob app/*.ts' will 48 expand to 'app/[id].ts', as expected. However, in posix 49 shells such as bash or zsh, the shell will first expand 50 'app/*.ts' to a list of filenames. Then glob will look 51 for a file matching 'app/[id].ts' (ie, 'app/i.ts' or 52 'app/d.ts'), which is unexpected. 53 54 Setting '--all' prevents this behavior, causing glob 55 to treat ALL patterns as glob expressions to be expanded, 56 even if they are an exact match to a file on disk. 57 58 When setting this option, be sure to enquote arguments 59 so that the shell will not expand them prior to passing 60 them to the glob command process. 61 `, 62 }, 63 absolute: { 64 short: 'a', 65 description: 'Expand to absolute paths', 66 }, 67 'dot-relative': { 68 short: 'd', 69 description: `Prepend './' on relative matches`, 70 }, 71 mark: { 72 short: 'm', 73 description: `Append a / on any directories matched`, 74 }, 75 posix: { 76 short: 'x', 77 description: `Always resolve to posix style paths, using '/' as the 78 directory separator, even on Windows. Drive letter 79 absolute matches on Windows will be expanded to their 80 full resolved UNC maths, eg instead of 'C:\\foo\\bar', 81 it will expand to '//?/C:/foo/bar'. 82 `, 83 }, 84 follow: { 85 short: 'f', 86 description: `Follow symlinked directories when expanding '**'`, 87 }, 88 realpath: { 89 short: 'R', 90 description: `Call 'fs.realpath' on all of the results. In the case 91 of an entry that cannot be resolved, the entry is 92 omitted. This incurs a slight performance penalty, of 93 course, because of the added system calls.`, 94 }, 95 stat: { 96 short: 's', 97 description: `Call 'fs.lstat' on all entries, whether required or not 98 to determine if it's a valid match.`, 99 }, 100 'match-base': { 101 short: 'b', 102 description: `Perform a basename-only match if the pattern does not 103 contain any slash characters. That is, '*.js' would be 104 treated as equivalent to '**/*.js', matching js files 105 in all directories. 106 `, 107 }, 108 dot: { 109 description: `Allow patterns to match files/directories that start 110 with '.', even if the pattern does not start with '.' 111 `, 112 }, 113 nobrace: { 114 description: 'Do not expand {...} patterns', 115 }, 116 nocase: { 117 description: `Perform a case-insensitive match. This defaults to 118 'true' on macOS and Windows platforms, and false on 119 all others. 120 121 Note: 'nocase' should only be explicitly set when it is 122 known that the filesystem's case sensitivity differs 123 from the platform default. If set 'true' on 124 case-insensitive file systems, then the walk may return 125 more or less results than expected. 126 `, 127 }, 128 nodir: { 129 description: `Do not match directories, only files. 130 131 Note: to *only* match directories, append a '/' at the 132 end of the pattern. 133 `, 134 }, 135 noext: { 136 description: `Do not expand extglob patterns, such as '+(a|b)'`, 137 }, 138 noglobstar: { 139 description: `Do not expand '**' against multiple path portions. 140 Ie, treat it as a normal '*' instead.`, 141 }, 142 'windows-path-no-escape': { 143 description: `Use '\\' as a path separator *only*, and *never* as an 144 escape character. If set, all '\\' characters are 145 replaced with '/' in the pattern.`, 146 }, 147}) 148 .num({ 149 'max-depth': { 150 short: 'D', 151 description: `Maximum depth to traverse from the current 152 working directory`, 153 }, 154}) 155 .opt({ 156 cwd: { 157 short: 'C', 158 description: 'Current working directory to execute/match in', 159 default: process.cwd(), 160 }, 161 root: { 162 short: 'r', 163 description: `A string path resolved against the 'cwd', which is 164 used as the starting point for absolute patterns that 165 start with '/' (but not drive letters or UNC paths 166 on Windows). 167 168 Note that this *doesn't* necessarily limit the walk to 169 the 'root' directory, and doesn't affect the cwd 170 starting point for non-absolute patterns. A pattern 171 containing '..' will still be able to traverse out of 172 the root directory, if it is not an actual root directory 173 on the filesystem, and any non-absolute patterns will 174 still be matched in the 'cwd'. 175 176 To start absolute and non-absolute patterns in the same 177 path, you can use '--root=' to set it to the empty 178 string. However, be aware that on Windows systems, a 179 pattern like 'x:/*' or '//host/share/*' will *always* 180 start in the 'x:/' or '//host/share/' directory, 181 regardless of the --root setting. 182 `, 183 }, 184 platform: { 185 description: `Defaults to the value of 'process.platform' if 186 available, or 'linux' if not. Setting --platform=win32 187 on non-Windows systems may cause strange behavior!`, 188 validate: v => new Set([ 189 'aix', 190 'android', 191 'darwin', 192 'freebsd', 193 'haiku', 194 'linux', 195 'openbsd', 196 'sunos', 197 'win32', 198 'cygwin', 199 'netbsd', 200 ]).has(v), 201 }, 202}) 203 .optList({ 204 ignore: { 205 short: 'i', 206 description: `Glob patterns to ignore`, 207 }, 208}) 209 .flag({ 210 debug: { 211 short: 'v', 212 description: `Output a huge amount of noisy debug information about 213 patterns as they are parsed and used to match files.`, 214 }, 215}) 216 .flag({ 217 help: { 218 short: 'h', 219 description: 'Show this usage information', 220 }, 221}); 222try { 223 const { positionals, values } = j.parse(); 224 if (values.help) { 225 console.log(j.usage()); 226 process.exit(0); 227 } 228 if (positionals.length === 0 && !values.default) 229 throw 'No patterns provided'; 230 if (positionals.length === 0 && values.default) 231 positionals.push(values.default); 232 const patterns = values.all 233 ? positionals 234 : positionals.filter(p => !existsSync(p)); 235 const matches = values.all 236 ? [] 237 : positionals.filter(p => existsSync(p)).map(p => join(p)); 238 const stream = globStream(patterns, { 239 absolute: values.absolute, 240 cwd: values.cwd, 241 dot: values.dot, 242 dotRelative: values['dot-relative'], 243 follow: values.follow, 244 ignore: values.ignore, 245 mark: values.mark, 246 matchBase: values['match-base'], 247 maxDepth: values['max-depth'], 248 nobrace: values.nobrace, 249 nocase: values.nocase, 250 nodir: values.nodir, 251 noext: values.noext, 252 noglobstar: values.noglobstar, 253 platform: values.platform, 254 realpath: values.realpath, 255 root: values.root, 256 stat: values.stat, 257 debug: values.debug, 258 posix: values.posix, 259 }); 260 const cmd = values.cmd; 261 if (!cmd) { 262 matches.forEach(m => console.log(m)); 263 stream.on('data', f => console.log(f)); 264 } 265 else { 266 stream.on('data', f => matches.push(f)); 267 stream.on('end', () => foregroundChild(cmd, matches, { shell: true })); 268 } 269} 270catch (e) { 271 console.error(j.usage()); 272 console.error(e instanceof Error ? e.message : String(e)); 273 process.exit(1); 274} 275//# sourceMappingURL=bin.mjs.map