fix: address review findings in repo_wizard.php
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Platform: mokoplatform CI / Gate 1: Code Quality (pull_request) Failing after 1m2s
Platform: mokoplatform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: mokoplatform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: mokoplatform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: mokoplatform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: mokoplatform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: mokoplatform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: mokoplatform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled

- Fix #1: replace nonexistent menu() with choose() using select()
- Fix #2: constructor — pass name only, not description as version
- Fix #3: respect --non-interactive flag (skip prompts, use defaults)
- Fix #4: use json_encode for composer/package.json (prevent injection)
- Fix #5: remove pointless count() wrapper
- Fix #6: validate --path exists or can be created before proceeding
- Fix TOML description escaping
This commit is contained in:
Jonathan Miller
2026-06-20 23:38:45 -05:00
parent 1cf076f088
commit 558cd6043d
+64 -46
View File
@@ -47,6 +47,9 @@ class RepoWizard extends CliFramework
/** Collected wizard answers. */
private array $answers = [];
/** When true, skip all interactive prompts and use defaults. */
private bool $nonInteractive = false;
protected function configure(): void
{
$this->setDescription('Interactive configuration wizard for new repositories');
@@ -57,16 +60,25 @@ class RepoWizard extends CliFramework
protected function run(): int
{
$targetPath = realpath($this->getArgument('--path', '.')) ?: $this->getArgument('--path', '.');
$rawPath = $this->getArgument('--path', '.');
$targetPath = realpath($rawPath) ?: $rawPath;
$this->nonInteractive = (bool) $this->getArgument('--non-interactive', false);
// Validate target path
if (!is_dir($targetPath) && !@mkdir($targetPath, 0755, true)) {
$this->log('ERROR', "Target path does not exist and cannot be created: {$targetPath}");
return self::EXIT_USAGE;
}
$targetPath = realpath($targetPath) ?: $targetPath;
$this->section('MokoCli Repository Wizard');
// ── Gather info ──────────────────────────────────────────────
$this->answers['name'] = $this->input('Repository name', basename($targetPath));
$this->answers['platform'] = $this->menu('Platform type', self::PLATFORMS, 'generic');
$this->answers['org'] = $this->input('Organization', 'MokoConsulting');
$this->answers['description'] = $this->input('Description', '');
$this->answers['license'] = $this->menu('License', self::LICENSES, 'GPL-3.0-or-later');
$this->answers['name'] = $this->ask('Repository name', basename($targetPath));
$this->answers['platform'] = $this->choose('Platform type', self::PLATFORMS, 'generic');
$this->answers['org'] = $this->ask('Organization', 'MokoConsulting');
$this->answers['description'] = $this->ask('Description', '');
$this->answers['license'] = $this->choose('License', self::LICENSES, 'GPL-3.0-or-later');
// ── Confirm ──────────────────────────────────────────────────
$this->section('Configuration Summary');
@@ -94,7 +106,7 @@ class RepoWizard extends CliFramework
}
$this->log('INFO', '');
$this->log('INFO', "Generated {$this->count($generated)} files in {$targetPath}");
$this->log('INFO', 'Generated ' . count($generated) . " files in {$targetPath}");
$this->log('INFO', 'Next: git init && git add -A && git commit -m "chore: initial scaffold"');
return 0;
@@ -209,7 +221,31 @@ class RepoWizard extends CliFramework
}
}
private function count(array $arr): int { return \count($arr); }
// ── Interactive helpers (respect --non-interactive) ─────────────
private function ask(string $prompt, string $default): string
{
if ($this->nonInteractive) {
return $default;
}
return $this->input($prompt, $default);
}
private function choose(string $prompt, array $options, string $default): string
{
if ($this->nonInteractive) {
return $default;
}
$keys = array_keys($options);
$labels = [];
foreach ($options as $key => $desc) {
$labels[] = "{$key}{$desc}";
}
$chosen = $this->select($prompt, $labels);
// Extract the key from "key — description"
$chosenKey = explode(' — ', $chosen, 2)[0] ?? $default;
return in_array($chosenKey, $keys, true) ? $chosenKey : $default;
}
// ── Template content ─────────────────────────────────────────────
@@ -266,24 +302,15 @@ MD;
private function composerJson(): string
{
$name = strtolower($this->answers['name']);
$desc = $this->answers['description'] ?: $this->answers['name'];
$license = $this->answers['license'];
return <<<JSON
{
"name": "mokoconsulting/{$name}",
"description": "{$desc}",
"type": "library",
"license": "{$license}",
"require": {
"php": ">=8.1"
},
"autoload": {
"psr-4": {}
}
}
JSON;
$data = [
'name' => 'mokoconsulting/' . strtolower($this->answers['name']),
'description' => $this->answers['description'] ?: $this->answers['name'],
'type' => 'library',
'license' => $this->answers['license'],
'require' => ['php' => '>=8.1'],
'autoload' => ['psr-4' => new \stdClass()],
];
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
private function phpcsXml(): string
@@ -310,24 +337,15 @@ NEON;
private function packageJson(): string
{
$name = strtolower($this->answers['name']);
$desc = $this->answers['description'] ?: $this->answers['name'];
return <<<JSON
{
"name": "@mokoconsulting/{$name}",
"version": "0.1.0",
"description": "{$desc}",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"devDependencies": {
"typescript": "^5.0"
}
}
JSON;
$data = [
'name' => '@mokoconsulting/' . strtolower($this->answers['name']),
'version' => '0.1.0',
'description' => $this->answers['description'] ?: $this->answers['name'],
'type' => 'module',
'scripts' => ['build' => 'tsc', 'start' => 'node dist/index.js'],
'devDependencies' => ['typescript' => '^5.0'],
];
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
private function tsconfigJson(): string
@@ -363,7 +381,7 @@ JSON;
private function pyprojectToml(): string
{
$name = strtolower($this->answers['name']);
$desc = $this->answers['description'] ?: $this->answers['name'];
$desc = str_replace(['\\', '"'], ['\\\\', '\\"'], $this->answers['description'] ?: $this->answers['name']);
return <<<TOML
[project]
@@ -407,5 +425,5 @@ GI;
}
}
$app = new RepoWizard('repo_wizard', 'Interactive configuration wizard for new repositories');
$app = new RepoWizard('repo_wizard');
exit($app->execute());