| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- #!/usr/bin/env node
- import { foregroundChild } from 'foreground-child';
- import { existsSync } from 'fs';
- import { jack } from 'jackspeak';
- import { loadPackageJson } from 'package-json-from-dist';
- import { basename, join } from 'path';
- import { globStream } from './index.js';
- const { version } = loadPackageJson(import.meta.url, '../package.json');
- const j = jack({
- usage: 'glob [options] [<pattern> [<pattern> ...]]',
- })
- .description(`
- Glob v${version}
- Expand the positional glob expression arguments into any matching file
- system paths found.
- `)
- .opt({
- cmd: {
- short: 'c',
- hint: 'command',
- description: `Run the command provided, passing the glob expression
- matches as arguments.`,
- },
- })
- .opt({
- default: {
- short: 'p',
- hint: 'pattern',
- description: `If no positional arguments are provided, glob will use
- this pattern`,
- },
- })
- .flag({
- shell: {
- default: false,
- description: `Interpret the command as a shell command by passing it
- to the shell, with all matched filesystem paths appended,
- **even if this cannot be done safely**.
- This is **not** unsafe (and usually unnecessary) when using
- the known Unix shells sh, bash, zsh, and fish, as these can
- all be executed in such a way as to pass positional
- arguments safely.
- **Note**: THIS IS UNSAFE IF THE FILE PATHS ARE UNTRUSTED,
- because a path like \`'some/path/\\$\\(cmd)'\` will be
- executed by the shell.
- If you do have positional arguments that you wish to pass to
- the command ahead of the glob pattern matches, use the
- \`--cmd-arg\`/\`-g\` option instead.
- The next major release of glob will fully remove the ability
- to use this option unsafely.`,
- },
- })
- .optList({
- 'cmd-arg': {
- short: 'g',
- hint: 'arg',
- default: [],
- description: `Pass the provided values to the supplied command, ahead of
- the glob matches.
- For example, the command:
- glob -c echo -g"hello" -g"world" *.txt
- might output:
- hello world a.txt b.txt
- This is a safer (and future-proof) alternative than putting
- positional arguments in the \`-c\`/\`--cmd\` option.`,
- },
- })
- .flag({
- all: {
- short: 'A',
- description: `By default, the glob cli command will not expand any
- arguments that are an exact match to a file on disk.
- This prevents double-expanding, in case the shell expands
- an argument whose filename is a glob expression.
- For example, if 'app/*.ts' would match 'app/[id].ts', then
- on Windows powershell or cmd.exe, 'glob app/*.ts' will
- expand to 'app/[id].ts', as expected. However, in posix
- shells such as bash or zsh, the shell will first expand
- 'app/*.ts' to a list of filenames. Then glob will look
- for a file matching 'app/[id].ts' (ie, 'app/i.ts' or
- 'app/d.ts'), which is unexpected.
- Setting '--all' prevents this behavior, causing glob
- to treat ALL patterns as glob expressions to be expanded,
- even if they are an exact match to a file on disk.
- When setting this option, be sure to enquote arguments
- so that the shell will not expand them prior to passing
- them to the glob command process.
- `,
- },
- absolute: {
- short: 'a',
- description: 'Expand to absolute paths',
- },
- 'dot-relative': {
- short: 'd',
- description: `Prepend './' on relative matches`,
- },
- mark: {
- short: 'm',
- description: `Append a / on any directories matched`,
- },
- posix: {
- short: 'x',
- description: `Always resolve to posix style paths, using '/' as the
- directory separator, even on Windows. Drive letter
- absolute matches on Windows will be expanded to their
- full resolved UNC paths, eg instead of 'C:\\foo\\bar',
- it will expand to '//?/C:/foo/bar'.
- `,
- },
- follow: {
- short: 'f',
- description: `Follow symlinked directories when expanding '**'`,
- },
- realpath: {
- short: 'R',
- description: `Call 'fs.realpath' on all of the results. In the case
- of an entry that cannot be resolved, the entry is
- omitted. This incurs a slight performance penalty, of
- course, because of the added system calls.`,
- },
- stat: {
- short: 's',
- description: `Call 'fs.lstat' on all entries, whether required or not
- to determine if it's a valid match.`,
- },
- 'match-base': {
- short: 'b',
- description: `Perform a basename-only match if the pattern does not
- contain any slash characters. That is, '*.js' would be
- treated as equivalent to '**/*.js', matching js files
- in all directories.
- `,
- },
- dot: {
- description: `Allow patterns to match files/directories that start
- with '.', even if the pattern does not start with '.'
- `,
- },
- nobrace: {
- description: 'Do not expand {...} patterns',
- },
- nocase: {
- description: `Perform a case-insensitive match. This defaults to
- 'true' on macOS and Windows platforms, and false on
- all others.
- Note: 'nocase' should only be explicitly set when it is
- known that the filesystem's case sensitivity differs
- from the platform default. If set 'true' on
- case-insensitive file systems, then the walk may return
- more or less results than expected.
- `,
- },
- nodir: {
- description: `Do not match directories, only files.
- Note: to *only* match directories, append a '/' at the
- end of the pattern.
- `,
- },
- noext: {
- description: `Do not expand extglob patterns, such as '+(a|b)'`,
- },
- noglobstar: {
- description: `Do not expand '**' against multiple path portions.
- Ie, treat it as a normal '*' instead.`,
- },
- 'windows-path-no-escape': {
- description: `Use '\\' as a path separator *only*, and *never* as an
- escape character. If set, all '\\' characters are
- replaced with '/' in the pattern.`,
- },
- })
- .num({
- 'max-depth': {
- short: 'D',
- description: `Maximum depth to traverse from the current
- working directory`,
- },
- })
- .opt({
- cwd: {
- short: 'C',
- description: 'Current working directory to execute/match in',
- default: process.cwd(),
- },
- root: {
- short: 'r',
- description: `A string path resolved against the 'cwd', which is
- used as the starting point for absolute patterns that
- start with '/' (but not drive letters or UNC paths
- on Windows).
- Note that this *doesn't* necessarily limit the walk to
- the 'root' directory, and doesn't affect the cwd
- starting point for non-absolute patterns. A pattern
- containing '..' will still be able to traverse out of
- the root directory, if it is not an actual root directory
- on the filesystem, and any non-absolute patterns will
- still be matched in the 'cwd'.
- To start absolute and non-absolute patterns in the same
- path, you can use '--root=' to set it to the empty
- string. However, be aware that on Windows systems, a
- pattern like 'x:/*' or '//host/share/*' will *always*
- start in the 'x:/' or '//host/share/' directory,
- regardless of the --root setting.
- `,
- },
- platform: {
- description: `Defaults to the value of 'process.platform' if
- available, or 'linux' if not. Setting --platform=win32
- on non-Windows systems may cause strange behavior!`,
- validOptions: [
- 'aix',
- 'android',
- 'darwin',
- 'freebsd',
- 'haiku',
- 'linux',
- 'openbsd',
- 'sunos',
- 'win32',
- 'cygwin',
- 'netbsd',
- ],
- },
- })
- .optList({
- ignore: {
- short: 'i',
- description: `Glob patterns to ignore`,
- },
- })
- .flag({
- debug: {
- short: 'v',
- description: `Output a huge amount of noisy debug information about
- patterns as they are parsed and used to match files.`,
- },
- version: {
- short: 'V',
- description: `Output the version (${version})`,
- },
- help: {
- short: 'h',
- description: 'Show this usage information',
- },
- });
- try {
- const { positionals, values } = j.parse();
- const { cmd, shell, all, default: def, version: showVersion, help, absolute, cwd, dot, 'dot-relative': dotRelative, follow, ignore, 'match-base': matchBase, 'max-depth': maxDepth, mark, nobrace, nocase, nodir, noext, noglobstar, platform, realpath, root, stat, debug, posix, 'cmd-arg': cmdArg, } = values;
- if (showVersion) {
- console.log(version);
- process.exit(0);
- }
- if (help) {
- console.log(j.usage());
- process.exit(0);
- }
- //const { shell, help } = values
- if (positionals.length === 0 && !def)
- throw 'No patterns provided';
- if (positionals.length === 0 && def)
- positionals.push(def);
- const patterns = all ? positionals : positionals.filter(p => !existsSync(p));
- const matches = all ? [] : positionals.filter(p => existsSync(p)).map(p => join(p));
- const stream = globStream(patterns, {
- absolute,
- cwd,
- dot,
- dotRelative,
- follow,
- ignore,
- mark,
- matchBase,
- maxDepth,
- nobrace,
- nocase,
- nodir,
- noext,
- noglobstar,
- platform: platform,
- realpath,
- root,
- stat,
- debug,
- posix,
- });
- if (!cmd) {
- matches.forEach(m => console.log(m));
- stream.on('data', f => console.log(f));
- }
- else {
- cmdArg.push(...matches);
- stream.on('data', f => cmdArg.push(f));
- // Attempt to support commands that contain spaces and otherwise require
- // shell interpretation, but do NOT shell-interpret the arguments, to avoid
- // injections via filenames. This affordance can only be done on known Unix
- // shells, unfortunately.
- //
- // 'bash', ['-c', cmd + ' "$@"', 'bash', ...matches]
- // 'zsh', ['-c', cmd + ' "$@"', 'zsh', ...matches]
- // 'fish', ['-c', cmd + ' "$argv"', ...matches]
- const { SHELL = 'unknown' } = process.env;
- const shellBase = basename(SHELL);
- const knownShells = ['sh', 'ksh', 'zsh', 'bash', 'fish'];
- if ((shell || /[ "']/.test(cmd)) &&
- knownShells.includes(shellBase)) {
- const cmdWithArgs = `${cmd} "\$${shellBase === 'fish' ? 'argv' : '@'}"`;
- if (shellBase !== 'fish') {
- cmdArg.unshift(SHELL);
- }
- cmdArg.unshift('-c', cmdWithArgs);
- stream.on('end', () => foregroundChild(SHELL, cmdArg));
- }
- else {
- if (shell) {
- process.emitWarning('The --shell option is unsafe, and will be removed. To pass ' +
- 'positional arguments to the subprocess, use -g/--cmd-arg instead.', 'DeprecationWarning', 'GLOB_SHELL');
- }
- stream.on('end', () => foregroundChild(cmd, cmdArg, { shell }));
- }
- }
- }
- catch (e) {
- console.error(j.usage());
- console.error(e instanceof Error ? e.message : String(e));
- process.exit(1);
- }
- //# sourceMappingURL=bin.mjs.map
|