Summary
The global flag parser in common/flags/index.ts runs in a static initializer at module-load time and throws Arg neither flag name nor flag value: <arg> whenever a positional argument appears after a --flag in process.argv. Because it runs before — and independently of — yargs, this crashes the entire CLI for otherwise-valid invocations where the user simply puts a positional after an option.
Reproduction
# Works — positional before the flag:
dataform run my_project --schema-suffix dev
# Crashes — same arguments, positional after the flag:
dataform run --schema-suffix dev my_project
Output:
Error: Arg neither flag name nor flag value: my_project
at .../common/flags/index.ts:71
at Module._compile (node:internal/modules/cjs/loader)
...
(Any command + any flag-before-positional combination triggers it, since the parser scans process.argv at import time regardless of the command.)
Expected behavior
Both argument orderings should work. yargs already parses options and positionals in any order; the lightweight common/flags parser shouldn't reject a valid ordering — and certainly shouldn't crash the process at load time.
Root cause
Flags.parsedArgv is built in a static initializer that scans process.argv. Once it sees any --flag it sets flagsStarted = true; a later non-flag token that isn't consumed as a flag value hits:
} else if (flagsStarted) {
throw new Error(`Arg neither flag name nor flag value: ${splitArg}`);
}
This treats positionals-after-flags as fatal. But this flag system coexists with yargs and only needs to extract --flag value pairs; positionals (the command, project dir, etc.) are yargs's responsibility and should be ignored, not fatal. The throw also fires at import time, so it takes down the CLI before any command handler runs.
Proposed fix
Make the parser lenient — ignore tokens that are neither a flag nor a flag value, instead of throwing:
let currentFlagName = "";
for (const splitArg of splitArgv) {
if (splitArg.startsWith("--")) {
currentFlagName = splitArg.slice(2);
parsedArgv[currentFlagName] = "";
} else if (currentFlagName) {
parsedArgv[currentFlagName] = splitArg;
currentFlagName = "";
}
// Non-flag args (command, positionals) are ignored — yargs parses those.
}
(Extracting the loop into a small pure function, e.g. parseArgvFlags(argv), also makes it unit-testable.)
Environment
- Verified present in
common/flags/index.ts on main.
- Happy to open a PR with the fix and a unit test if that's useful.
Summary
The global flag parser in
common/flags/index.tsruns in a static initializer at module-load time and throwsArg neither flag name nor flag value: <arg>whenever a positional argument appears after a--flaginprocess.argv. Because it runs before — and independently of — yargs, this crashes the entire CLI for otherwise-valid invocations where the user simply puts a positional after an option.Reproduction
Output:
(Any command + any flag-before-positional combination triggers it, since the parser scans
process.argvat import time regardless of the command.)Expected behavior
Both argument orderings should work. yargs already parses options and positionals in any order; the lightweight
common/flagsparser shouldn't reject a valid ordering — and certainly shouldn't crash the process at load time.Root cause
Flags.parsedArgvis built in a static initializer that scansprocess.argv. Once it sees any--flagit setsflagsStarted = true; a later non-flag token that isn't consumed as a flag value hits:This treats positionals-after-flags as fatal. But this flag system coexists with yargs and only needs to extract
--flag valuepairs; positionals (the command, project dir, etc.) are yargs's responsibility and should be ignored, not fatal. The throw also fires at import time, so it takes down the CLI before any command handler runs.Proposed fix
Make the parser lenient — ignore tokens that are neither a flag nor a flag value, instead of throwing:
(Extracting the loop into a small pure function, e.g.
parseArgvFlags(argv), also makes it unit-testable.)Environment
common/flags/index.tsonmain.