class

Analyzer::Javascript::Cli

Inherits CliEndpointSupport < Analyzer::Javascript::JavascriptEngine < Analyzer < FileHelper < Reference < Object

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.

Constants

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 inside subCommands: { key: NAME }, instead of nesting the defineCommand call inline. resolve_citty_var_subcommands resolves those same-file references so args: 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 own defineCommand object inside subCommands, 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-declared const optionDefinitions = [...] array (the library's own documented convention), or an inline commandLineArgs([...]) 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 unrelated alias/default object 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 own getopts builtin, a stray comment, or an ordinary identifier fragment), so they require an actual import statement rather than content.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 methods

tech_name
Source

Instance methods

analyze
Source
tech

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.

Source