fix: camelCase detection mechanism in global CLI
This commit is contained in:
@@ -27,6 +27,22 @@ const initScriptPath = resolve(__dirname, '../scripts/init.js');
|
|||||||
|
|
||||||
// Helper function to run dev.js with arguments
|
// Helper function to run dev.js with arguments
|
||||||
function runDevScript(args) {
|
function runDevScript(args) {
|
||||||
|
// Debug: Show the transformed arguments when DEBUG=1 is set
|
||||||
|
if (process.env.DEBUG === '1') {
|
||||||
|
console.error('\nDEBUG - CLI Wrapper Analysis:');
|
||||||
|
console.error('- Original command: ' + process.argv.join(' '));
|
||||||
|
console.error('- Transformed args: ' + args.join(' '));
|
||||||
|
console.error('- dev.js will receive: node ' + devScriptPath + ' ' + args.join(' ') + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing: If TEST_MODE is set, just print args and exit
|
||||||
|
if (process.env.TEST_MODE === '1') {
|
||||||
|
console.log('Would execute:');
|
||||||
|
console.log(`node ${devScriptPath} ${args.join(' ')}`);
|
||||||
|
process.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const child = spawn('node', [devScriptPath, ...args], {
|
const child = spawn('node', [devScriptPath, ...args], {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
cwd: process.cwd()
|
cwd: process.cwd()
|
||||||
@@ -44,93 +60,128 @@ function runDevScript(args) {
|
|||||||
*/
|
*/
|
||||||
function createDevScriptAction(commandName) {
|
function createDevScriptAction(commandName) {
|
||||||
return (options, cmd) => {
|
return (options, cmd) => {
|
||||||
// Start with the command name
|
// Helper function to detect camelCase and convert to kebab-case
|
||||||
|
const toKebabCase = (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
|
||||||
|
// Check for camelCase flags and error out with helpful message
|
||||||
|
const camelCaseFlags = [];
|
||||||
|
for (const arg of process.argv) {
|
||||||
|
if (arg.startsWith('--') && /[A-Z]/.test(arg)) {
|
||||||
|
const flagName = arg.split('=')[0].slice(2); // Remove -- and anything after =
|
||||||
|
const kebabVersion = toKebabCase(flagName);
|
||||||
|
camelCaseFlags.push({
|
||||||
|
original: flagName,
|
||||||
|
kebabCase: kebabVersion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If camelCase flags were found, show error and exit
|
||||||
|
if (camelCaseFlags.length > 0) {
|
||||||
|
console.error('\nError: Please use kebab-case for CLI flags:');
|
||||||
|
camelCaseFlags.forEach(flag => {
|
||||||
|
console.error(` Instead of: --${flag.original}`);
|
||||||
|
console.error(` Use: --${flag.kebabCase}`);
|
||||||
|
});
|
||||||
|
console.error('\nExample: task-master parse-prd --num-tasks=5 instead of --numTasks=5\n');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we've ensured no camelCase flags, we can now just:
|
||||||
|
// 1. Start with the command name
|
||||||
const args = [commandName];
|
const args = [commandName];
|
||||||
|
|
||||||
// Handle direct arguments (non-option arguments)
|
// 3. Get positional arguments and explicit flags from the command line
|
||||||
if (cmd && cmd.args && cmd.args.length > 0) {
|
const commandArgs = [];
|
||||||
args.push(...cmd.args);
|
const positionals = new Set(); // Track positional args we've seen
|
||||||
}
|
|
||||||
|
|
||||||
// Get the original CLI arguments to detect which options were explicitly specified
|
// Find the command in raw process.argv to extract args
|
||||||
const originalArgs = process.argv;
|
const commandIndex = process.argv.indexOf(commandName);
|
||||||
|
if (commandIndex !== -1) {
|
||||||
// Special handling for parent parameter which seems to have issues
|
// Process all args after the command name
|
||||||
const parentArg = originalArgs.find(arg => arg.startsWith('--parent='));
|
for (let i = commandIndex + 1; i < process.argv.length; i++) {
|
||||||
if (parentArg) {
|
const arg = process.argv[i];
|
||||||
args.push('-p', parentArg.split('=')[1]);
|
|
||||||
} else if (options.parent) {
|
if (arg.startsWith('--')) {
|
||||||
args.push('-p', options.parent);
|
// It's a flag - pass through as is
|
||||||
}
|
commandArgs.push(arg);
|
||||||
|
// Skip the next arg if this is a flag with a value (not --flag=value format)
|
||||||
// Add all options
|
if (!arg.includes('=') &&
|
||||||
Object.entries(options).forEach(([key, value]) => {
|
i + 1 < process.argv.length &&
|
||||||
// Skip the Command's built-in properties and parent (special handling)
|
!process.argv[i+1].startsWith('--')) {
|
||||||
if (['parent', 'commands', 'options', 'rawArgs'].includes(key)) {
|
commandArgs.push(process.argv[++i]);
|
||||||
return;
|
}
|
||||||
}
|
} else if (!positionals.has(arg)) {
|
||||||
|
// It's a positional argument we haven't seen
|
||||||
// Special case: handle the 'generate' option which is automatically set to true
|
commandArgs.push(arg);
|
||||||
// We should only include it if --no-generate was explicitly specified
|
positionals.add(arg);
|
||||||
if (key === 'generate') {
|
|
||||||
// Check if --no-generate was explicitly specified
|
|
||||||
if (originalArgs.includes('--no-generate')) {
|
|
||||||
args.push('--no-generate');
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all command line args we collected
|
||||||
|
args.push(...commandArgs);
|
||||||
|
|
||||||
|
// 4. Add default options from Commander if not specified on command line
|
||||||
|
// Track which options we've seen on the command line
|
||||||
|
const userOptions = new Set();
|
||||||
|
for (const arg of commandArgs) {
|
||||||
|
if (arg.startsWith('--')) {
|
||||||
|
// Extract option name (without -- and value)
|
||||||
|
const name = arg.split('=')[0].slice(2);
|
||||||
|
userOptions.add(name);
|
||||||
|
|
||||||
|
// Add the kebab-case version too, to prevent duplicates
|
||||||
|
const kebabName = name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
userOptions.add(kebabName);
|
||||||
|
|
||||||
|
// Add the camelCase version as well
|
||||||
|
const camelName = kebabName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||||
|
userOptions.add(camelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Commander-provided defaults for options not specified by user
|
||||||
|
Object.entries(options).forEach(([key, value]) => {
|
||||||
|
// Skip built-in Commander properties and options the user provided
|
||||||
|
if (['parent', 'commands', 'options', 'rawArgs'].includes(key) || userOptions.has(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for how this parameter was passed in the original arguments
|
// Also check the kebab-case version of this key
|
||||||
// Find if it was passed as --key=value
|
|
||||||
const equalsFormat = originalArgs.find(arg => arg.startsWith(`--${key}=`));
|
|
||||||
|
|
||||||
// Check for kebab-case flags
|
|
||||||
// Convert camelCase back to kebab-case for command line arguments
|
|
||||||
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
if (userOptions.has(kebabKey)) {
|
||||||
// Check if it was passed with kebab-case
|
|
||||||
const foundInOriginal = originalArgs.find(arg =>
|
|
||||||
arg === `--${key}` ||
|
|
||||||
arg === `--${kebabKey}` ||
|
|
||||||
arg.startsWith(`--${key}=`) ||
|
|
||||||
arg.startsWith(`--${kebabKey}=`)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Determine the actual flag name to use (original or kebab-case)
|
|
||||||
const flagName = foundInOriginal ?
|
|
||||||
(foundInOriginal.startsWith('--') ? foundInOriginal.split('=')[0].slice(2) : key) :
|
|
||||||
key;
|
|
||||||
|
|
||||||
if (equalsFormat) {
|
|
||||||
// Preserve the original format with equals sign
|
|
||||||
args.push(equalsFormat);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle boolean flags
|
// Add default values
|
||||||
if (typeof value === 'boolean') {
|
if (value !== undefined) {
|
||||||
if (value === true) {
|
if (typeof value === 'boolean') {
|
||||||
// For non-negated options, add the flag
|
if (value === true) {
|
||||||
if (!flagName.startsWith('no-')) {
|
args.push(`--${key}`);
|
||||||
args.push(`--${flagName}`);
|
} else if (value === false && key === 'generate') {
|
||||||
|
args.push('--no-generate');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For false values, use --no-X format
|
args.push(`--${key}=${value}`);
|
||||||
if (flagName.startsWith('no-')) {
|
|
||||||
// If option is already in --no-X format, it means the user used --no-X explicitly
|
|
||||||
// We need to pass it as is
|
|
||||||
args.push(`--${flagName}`);
|
|
||||||
} else {
|
|
||||||
// If it's a regular option set to false, convert to --no-X
|
|
||||||
args.push(`--no-${flagName}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (value !== undefined) {
|
|
||||||
// For non-boolean values, pass as --key value (space-separated)
|
|
||||||
args.push(`--${flagName}`, value.toString());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Special handling for parent parameter (uses -p)
|
||||||
|
if (options.parent && !args.includes('-p') && !userOptions.has('parent')) {
|
||||||
|
args.push('-p', options.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug output for troubleshooting
|
||||||
|
if (process.env.DEBUG === '1') {
|
||||||
|
console.error('DEBUG - Command args:', commandArgs);
|
||||||
|
console.error('DEBUG - User options:', Array.from(userOptions));
|
||||||
|
console.error('DEBUG - Commander options:', options);
|
||||||
|
console.error('DEBUG - Final args:', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the script with our processed args
|
||||||
runDevScript(args);
|
runDevScript(args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -214,7 +265,8 @@ tempProgram.commands.forEach(cmd => {
|
|||||||
// Create a new command with the same name and description
|
// Create a new command with the same name and description
|
||||||
const newCmd = program
|
const newCmd = program
|
||||||
.command(cmd.name())
|
.command(cmd.name())
|
||||||
.description(cmd.description());
|
.description(cmd.description())
|
||||||
|
.allowUnknownOption(); // Allow any options, including camelCase ones
|
||||||
|
|
||||||
// Copy all options
|
// Copy all options
|
||||||
cmd.options.forEach(opt => {
|
cmd.options.forEach(opt => {
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
* It imports functionality from the modules directory and provides a CLI.
|
* It imports functionality from the modules directory and provides a CLI.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Add at the very beginning of the file
|
||||||
|
if (process.env.DEBUG === '1') {
|
||||||
|
console.error('DEBUG - dev.js received args:', process.argv.slice(2));
|
||||||
|
}
|
||||||
|
|
||||||
import { runCLI } from './modules/commands.js';
|
import { runCLI } from './modules/commands.js';
|
||||||
|
|
||||||
// Run the CLI with the process arguments
|
// Run the CLI with the process arguments
|
||||||
|
|||||||
Reference in New Issue
Block a user