class
Analyzer::Javascript::Cli
Overview
Surfaces the command-line attack surface of JavaScript/TypeScript programs
(Node, Deno, Bun) as cli:// endpoints: one endpoint per (sub)command
with named options (param_type "flag"), positional arguments ("argument")
and consumed environment variables ("env"). Covers the util.parseArgs
builtin plus commander, yargs, cac, sade, meow, minimist, arg,
command-line-args, getopts and citty. A single analyzer scans every JS/TS
extension so a .ts CLI isn't double-counted.
Included Modules
Defined in:
analyzer/analyzers/javascript/cli.crConstant Summary
-
ARG_CALL_RE =
/\barg\s*\(\s*\{/ -
arg:
arg({ '--name': String, '-n': '--name' }). Keys whose value is a quoted string are aliases pointing at the canonical flag and are skipped so an alias doesn't surface as a second, meaningless flag. -
ARG_KEY_RE =
/^\s*['"](-{1,2}[A-Za-z][\w-]*)['"]\s*:\s*(.+?),?\s*$/ -
ARGUMENT_RE =
/\.\s*argument\s*\(\s*['"][<\[]?\.{0,3}([A-Za-z0-9_-]+)/ -
ARGV_SLICE =
/\bprocess\.argv\.slice\s*\(\s*2\s*\)/ -
BUN_ARGV =
/\bBun\.argv\b/ -
BUN_ENV =
/\bBun\.env\.([A-Za-z_][A-Za-z0-9_]*)/ -
CITTY_ARGS_HEADER =
/\bargs\s*:\s*\{/ -
CITTY_DEFINE_ASSIGN_RE =
/^\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*defineCommand\s*\(\s*\{/ -
citty also allows each subcommand to be declared as its own top-level
const NAME = defineCommand({...})and merely referenced by identifier insidesubCommands: { key: NAME }, instead of nesting thedefineCommandcall inline.resolve_citty_var_subcommandsresolves those same-file references soargs:inside NAME's block attributes to<root>/<key>rather than falling back to root. -
CITTY_SUBCOMMAND_RE =
/^\s*['"]?([A-Za-z_$][\w$-]*)['"]?\s*:\s*defineCommand\s*\(\s*\{/ -
citty:
serve: defineCommand({ args: { port: { type: 'string' } } }). Subcommands nest their owndefineCommandobject insidesubCommands, so args are attributed via a brace-depth stack (never a sticky cursor). -
CITTY_SUBCOMMAND_REF_RE =
/^\s*['"]?([A-Za-z_$][\w$-]*)['"]?\s*:\s*([A-Za-z_$][\w$]*)\s*,?\s*$/ -
CITTY_SUBCOMMANDS_HEADER =
/\bsubCommands\s*:\s*\{/ -
CLA_ARRAY_ASSIGN_RE =
/^\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*\[/ -
command-line-args:
commandLineArgs(optionDefinitions)referencing a separately-declaredconst optionDefinitions = [...]array (the library's own documented convention), or an inlinecommandLineArgs([...])array. Matching is bounded to the resolved array's own brackets (never a whole-file scan) so an unrelated same-shaped object literal elsewhere in the file (e.g. a content-field schema) is never picked up as a bogus flag, and each{...}entry is scanned as its own brace-bounded block so Prettier-style multi-line-per-key formatting isn't silently dropped. -
CLA_CALL_INLINE_RE =
/\bcommandLineArgs\s*\(\s*\[/ -
CLA_CALL_VAR_RE =
/\bcommandLineArgs\s*\(\s*([A-Za-z_$][\w$]*)\s*[,)]/ -
CLA_NAME_RE =
/\bname\s*:\s*['"]([A-Za-z0-9_-]+)['"]/ -
CLA_TYPE_KEY_RE =
/\btype\s*:/ -
CLI_EVIDENCE_RE =
Regex.union(CLI_MARKERS_RE, ARGV_SLICE, LIB_IMPORT_ONLY_RE) -
Two more OR-ed pairs of whole-file scans, collapsed the same way: the evidence gate every JS/TS file passes through, and the web-framework veto that decides whether env reads are emitted.
-
CLI_MARKERS_RE =
Regex.union("commander", "yargs", "cac", "meow", "minimist", "clipanion", "@oclif", "sade", "parseArgs", "Deno.args", "Bun.argv") -
Precompiled once — a single PCRE2-JIT scan replaces up to eleven naive String#includes? substring scans over the whole file content. Regex.union auto-escapes each literal (including the dots in "Deno.args"/"Bun.argv"), so it is provably equivalent to the OR-of-includes? it replaces.
-
COMMAND_RE =
/\.\s*command\s*\(\s*['"]([^'"]+)['"]/ -
commander / cac / sade / yargs subcommand. The command string may carry an arg spec (
serve <port>), so the first whitespace token is the name. -
DENO_ARGS =
/\bDeno\.args\b/ -
builtin / runtime argv markers (root surface).
-
DENO_ENV =
/\bDeno\.env\.get\s*\(\s*['"]([^'"]+)['"]/ -
ENV_DOT =
/\bprocess\.env\.([A-Za-z_][A-Za-z0-9_]*)/ -
env reads (gated). NODE_ENV is config plumbing, not an input.
-
ENV_INDEX =
/\bprocess\.env\s*\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]/ -
GETOPTS_ALIAS_HEADER =
/\balias\s*:\s*\{/ -
GETOPTS_BOOLEAN_HEADER =
/\bboolean\s*:\s*\[/ -
GETOPTS_CALL_RE =
/\bgetopts\s*\(/ -
getopts:
getopts(process.argv.slice(2), { alias: {...}, default: {...}, boolean: [...], string: [...] }). Bounded to the call's own parens so unrelatedalias/defaultobject literals elsewhere aren't picked up. -
GETOPTS_DEFAULT_HEADER =
/\bdefault\s*:\s*\{/ -
GETOPTS_STRING_HEADER =
/\bstring\s*:\s*\[/ -
LIB_IMPORT_ONLY_RE =
/(?:require\s*\(\s*|from\s+)['"](?:arg|getopts|citty|command-line-args)['"]/ -
require('arg')/from 'arg', and likewise for getopts/citty/ command-line-args — these are short/generic tokens too easy to trip as a bare substring (e.g. bash's owngetoptsbuiltin, a stray comment, or an ordinary identifier fragment), so they require an actual import statement rather thancontent.includes?. -
LONG_FLAG_RE =
/(--[A-Za-z0-9][\w-]*)/ -
MEOW_FLAGS_HEADER =
/\bflags\s*:\s*\{/ -
NEW_CHAIN_RE =
/^[A-Za-z_$][\w$]*\s*\.\s*(?:command|option|argument)\b/ -
A fresh statement that chains command/option/argument off an identifier (
program.option(...),cli.command(...)) starts at the root program, not the previously-seen subcommand — used to reset the cursor so a global option declared after a subcommand isn't mis-attributed to it. -
OBJECT_KEY_RE =
/^\s*['"]?([A-Za-z_$][\w$-]*)['"]?\s*:/ -
OBJECT_VALUE_RE =
/^\s*['"]?[A-Za-z_$][\w$-]*['"]?\s*:\s*['"]([A-Za-z0-9_-]+)['"]/ -
OPTION_RE =
/\.\s*(?:required)?[Oo]ption\s*\(\s*['"]([^'"]+)['"]/ -
commander/cac/sade:
.option('-p, --port <n>'); yargs:.option('port'). -
PARSEARGS_HEADER =
/\boptions\s*:\s*\{/ -
object-literal schema headers (keys become root flags).
-
POSITIONAL_RE =
/\.\s*positional\s*\(\s*['"]([^'"]+)['"]/ -
SHORT_FLAG_RE =
/(-[A-Za-z0-9])\b/ -
SOURCE_EXTS =
[".js", ".mjs", ".cjs", ".jsx", ".ts", ".mts", ".cts", ".tsx"] -
STRING_LIT_RE =
/['"]([A-Za-z0-9_.\/-]+)['"]/ -
WEB_EVIDENCE_RE =
Regex.union(WEB_FRAMEWORK_RE, WEB_LISTEN_RE) -
WEB_FRAMEWORK_RE =
/(?:require\s*\(\s*|from\s+)['"](?:express|fastify|koa|@hapi\/hapi|@nestjs\/[\w-]+|next|nuxt|hono|@hono\/[\w-]+|restify|@adonisjs\/[\w-]+|elysia|polka|connect|h3|@sveltejs\/[\w-]+|@remix-run\/[\w-]+|apollo-server|@apollo\/server)['"]/ -
WEB_LISTEN_RE =
/\.\s*listen\s*\(|\bcreateServer\s*\(/
Class Method Summary
Instance Method Summary
- #analyze
-
#tech : String
Instance-side view of the same declaration.
Instance methods inherited from class Analyzer
analyze
analyze,
base_path : String
base_path,
base_paths : Array(String)
base_paths,
base_relative_path(path : String) : String
base_relative_path,
callees_needed? : Bool
callees_needed?,
content_matches?(content : String, markers : Regex) : Bool
content_matches?,
http_header_name(name : String) : String | Nil
http_header_name,
line_number_for_index(content : String, char_index : Int32) : Int32
line_number_for_index,
logger : NoirLogger
logger,
parallel_analyze(files : Array(String), &block : String -> Nil)
parallel_analyze,
read_file_content(path : String) : String
read_file_content,
result : Array(Endpoint)
result,
tech : String
tech,
unique_params(params : Array(Param)) : Array(Param)
unique_params,
url : String
url,
web_root_path(path : String, markers : Array(String)) : String
web_root_path
Constructor methods inherited from class Analyzer
new(options : Hash(String, YAML::Any))
new
Macros inherited from class Analyzer
analyzer_for(tech)
analyzer_for
Instance methods inherited from module FileHelper
all_files : Array(String)
all_files,
get_files_by_basename(basename : String) : Array(String)
get_files_by_basename,
get_files_by_extension(extension : String) : Array(String)
get_files_by_extension,
get_files_by_extensions(extensions : Array(String)) : Array(String)
get_files_by_extensions,
get_files_by_prefix(prefix : String) : Array(String)
get_files_by_prefix,
get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String)
get_files_by_prefix_and_extension,
get_files_by_relative_path(relative_path : String, root : String = "") : Array(String)
get_files_by_relative_path,
get_public_dir_files(base_path : String, folder : String) : Array(String)
get_public_dir_files,
get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String)
get_public_files,
walked_path(expanded : String) : String
walked_path
Class Method Detail
Instance Method Detail
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.