Skip to content

Add run-template:clone command - #56

Merged
Vitexus merged 1 commit into
mainfrom
fix/runtemplate-clone-disabled
Aug 12, 2026
Merged

Add run-template:clone command#56
Vitexus merged 1 commit into
mainfrom
fix/runtemplate-clone-disabled

Conversation

@Vitexus

@Vitexus Vitexus commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

  • New run-template:clone --id=<id> [--name=<name>] [--format=text|json] command, mirroring the web UI's clone action.
  • Uses RunTemplate::cloneAs() (core) which copies config, saved env values, and credential assignments into a new run template and always creates it disabled (active=false), regardless of the source template's state — a copied config can't be picked up by the scheduler before it's reviewed.
  • Docs updated in multiflexi-doc-en (source/reference/cli.rst): command list, options note, and a usage example.

Depends on: VitexSoftware/php-vitexsoftware-multiflexi-core#53 (adds cloneAs()) — needs that merged/released first.

Companion fix for the same underlying bug in the web UI: VitexSoftware/multiflexi-web#7, VitexSoftware/multiflexi-web5#4.

Test plan

  • php -l on changed/new files
  • multiflexi-cli run-template:clone --id=<id>, confirm printed new ID and that the row has active=0

Summary by CodeRabbit

  • New Features
    • Added a CLI command to clone existing run templates.
    • Supports specifying a source template ID and optional name for the clone.
    • Provides text and JSON output formats.
    • New clones are created disabled by default.
    • Reports validation and cloning errors with appropriate command status codes.

Mirrors the web UI's clone action: copies a run template's config,
saved env values, and credential assignments into a new run template
via RunTemplate::cloneAs() (core). The clone is always created
disabled, so a copied config can't be picked up by the scheduler
before it's reviewed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the run-template:clone command. The command accepts a source ID, optional name, and output format. It validates the source, creates a disabled clone, reports results, and registers with the CLI application.

Changes

Run template cloning

Layer / File(s) Summary
Clone command flow
src/Command/RunTemplate/CloneCommand.php
Defines the command options. Validates the source ID and template. Creates a disabled clone and reports success or failure in text or JSON.
CLI registration
src/multiflexi-cli.php
Imports and registers RunTemplateCloneCommand with the Symfony console application.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SymfonyConsoleApplication
  participant CloneCommand
  participant RunTemplate
  SymfonyConsoleApplication->>CloneCommand: Execute run-template:clone
  CloneCommand->>RunTemplate: Validate source and invoke cloneAs
  RunTemplate-->>CloneCommand: Return cloned template
  CloneCommand-->>SymfonyConsoleApplication: Output ID and disabled status
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding the run-template:clone command.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runtemplate-clone-disabled

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Command/RunTemplate/CloneCommand.php`:
- Around line 40-52: Update the --id validation in the command before
constructing RunTemplate: require the raw option value to be a string containing
only a positive integer, reject values such as 12abc, zero, and negatives with
the existing failure response, and only then cast the validated value to int.
- Around line 31-34: Update the user-facing strings in the CloneCommand
definition and execution flow, including the command description, option
descriptions, error messages, and success output, to use _(). Preserve JSON
field names such as status and runtemplate_id unchanged, and retain the existing
translated handling of “Clone.”
- Around line 23-37: Add PHPDoc blocks for CloneCommand, configure(), and
execute(), describing the command’s cloning purpose, method parameters, and
execute() return value. Keep the existing command behavior unchanged and follow
the project’s documentation conventions.
- Line 23: Add a PHPUnit test class for CloneCommand covering invalid IDs,
supported output formats, clone failures, and successful cloning from an active
source producing an inactive clone. Follow the existing command test conventions
and exercise CloneCommand through its public command behavior.
- Line 39: Update the format handling in CloneCommand to define constants for
the supported text and JSON formats, validate the --format value before
processing, and return failure for any unsupported value such as xml. Use the
constants for both validation and subsequent format selection.
- Line 65: Before the cloneAs call in the RunTemplate clone flow, align the
multiflexi-core dependency to a reachable revision that provides cloneAs, or
replace it with the supported cloning API. Update the lockfile accordingly, then
add an integration test covering the inactive state and the documented failure
behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 88194867-20fa-4e63-bc1a-c986871f7b9e

📥 Commits

Reviewing files that changed from the base of the PR and between 189f89c and 13f9ba0.

📒 Files selected for processing (2)
  • src/Command/RunTemplate/CloneCommand.php
  • src/multiflexi-cli.php

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class CloneCommand extends BaseCommand

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add PHPUnit coverage for the new command.

The provided changes add CloneCommand without its PHPUnit test file. Test invalid IDs, output formats, clone failures, and that an active source creates an inactive clone.

As per coding guidelines, create or update a PHPUnit test file whenever a class is created or updated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` at line 23, Add a PHPUnit test
class for CloneCommand covering invalid IDs, supported output formats, clone
failures, and successful cloning from an active source producing an inactive
clone. Follow the existing command test conventions and exercise CloneCommand
through its public command behavior.

Source: Coding guidelines

Comment on lines +23 to +37
class CloneCommand extends BaseCommand
{
protected static $defaultName = 'run-template:clone';

protected function configure(): void
{
$this
->setName('run-template:clone')
->setDescription('Clone a run template; the clone is always created disabled')
->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'Output format: text or json', 'text')
->addOption('id', null, InputOption::VALUE_REQUIRED, 'Source RunTemplate ID')
->addOption('name', null, InputOption::VALUE_OPTIONAL, 'Name for the clone (default: "<source name> Clone")');
}

protected function execute(InputInterface $input, OutputInterface $output): int

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add docblocks for the class and methods.

CloneCommand, configure(), and execute() have no docblocks. Document the command purpose, parameters, and return value.

As per coding guidelines, **/*.php requires a docblock for every function and class.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` around lines 23 - 37, Add PHPDoc
blocks for CloneCommand, configure(), and execute(), describing the command’s
cloning purpose, method parameters, and execute() return value. Keep the
existing command behavior unchanged and follow the project’s documentation
conventions.

Source: Coding guidelines

Comment on lines +31 to +34
->setDescription('Clone a run template; the clone is always created disabled')
->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'Output format: text or json', 'text')
->addOption('id', null, InputOption::VALUE_REQUIRED, 'Source RunTemplate ID')
->addOption('name', null, InputOption::VALUE_OPTIONAL, 'Name for the clone (default: "<source name> Clone")');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Translate all user-visible strings.

Only "Clone" uses _(). Wrap command descriptions, option descriptions, error messages, and success output in _(). Keep JSON field names such as status and runtemplate_id unchanged.

As per coding guidelines, src/**/*.php requires _() calls for internationalization messages.

Also applies to: 44-46, 56-59, 64-64, 69-71, 78-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` around lines 31 - 34, Update the
user-facing strings in the CloneCommand definition and execution flow, including
the command description, option descriptions, error messages, and success
output, to use _(). Preserve JSON field names such as status and runtemplate_id
unchanged, and retain the existing translated handling of “Clone.”

Source: Coding guidelines


protected function execute(InputInterface $input, OutputInterface $output): int
{
$format = strtolower($input->getOption('format'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject unsupported output formats.

--format=xml falls through to text output. Validate text and json before processing the command, then return failure for every other value. Define the repeated format values as constants.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` at line 39, Update the format
handling in CloneCommand to define constants for the supported text and JSON
formats, validate the --format value before processing, and return failure for
any unsupported value such as xml. Use the constants for both validation and
subsequent format selection.

Comment on lines +40 to +52
$id = $input->getOption('id');

if (empty($id)) {
if ($format === 'json') {
$output->writeln(json_encode(['status' => 'error', 'message' => 'Missing --id'], \JSON_PRETTY_PRINT));
} else {
$output->writeln('<error>Missing --id</error>');
}

return self::FAILURE;
}

$source = new RunTemplate((int) $id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the source ID before casting it.

--id=12abc passes the empty check and becomes ID 12. The command can clone a different template than the caller selected. Require a positive integer string before the (int) cast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` around lines 40 - 52, Update the
--id validation in the command before constructing RunTemplate: require the raw
option value to be a string containing only a positive integer, reject values
such as 12abc, zero, and negatives with the existing failure response, and only
then cast the validated value to int.

}

$newName = $input->getOption('name') ?: $source->getRecordName().' '._('Clone');
$newId = $source->cloneAs($newName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify the resolved core dependency and inspect available clone-state handling.
rg -n -C 6 '"vitexsoftware/multiflexi-core"|cloneAs\s*\(|setActive\s*\(|\bactive\b' \
  . -g 'composer.json' -g 'composer.lock' -g '*.php' || true

Repository: VitexSoftware/multiflexi-cli

Length of output: 21125


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- CloneCommand.php ---'
cat -n src/Command/RunTemplate/CloneCommand.php

printf '%s\n' '--- locked dependency metadata ---'
sed -n '3568,3595p' composer.lock

printf '%s\n' '--- upstream cloneAs references at the locked commit ---'
commit='80eefa9362998179f5c68374ec5dc77fd605cc57'
curl --fail --silent --show-error \
  "https://api.github.com/repos/VitexSoftware/php-vitexsoftware-multiflexi-core/commits/${commit}" |
  jq -r '.sha, .html_url, (.files[]?.filename)' | head -80

curl --fail --silent --show-error \
  "https://api.github.com/repos/VitexSoftware/php-vitexsoftware-multiflexi-core/git/trees/${commit}?recursive=1" |
  jq -r '.tree[] | select(.path | endswith(".php")) | .path' |
  while IFS= read -r path; do
    content_url="https://raw.githubusercontent.com/VitexSoftware/php-vitexsoftware-multiflexi-core/${commit}/${path}"
    body="$(curl --fail --silent --show-error "$content_url" || true)"
    if printf '%s' "$body" | grep -qE 'cloneAs|class RunTemplate'; then
      printf '%s\n' "--- ${path} ---"
      printf '%s\n' "$body" | grep -n -C 12 -E 'cloneAs|class RunTemplate'
    fi
  done

Repository: VitexSoftware/multiflexi-cli

Length of output: 5049


🏁 Script executed:

#!/bin/bash
set -u

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
commit='80eefa9362998179f5c68374ec5dc77fd605cc57'

printf '%s\n' '--- exact commit API response ---'
curl -sS -w '\nHTTP %{http_code}\n' \
  "https://api.github.com/repos/${repo}/commits/${commit}" |
  jq '{sha, message: .commit.message, url: .html_url, status: .status, documentation_url, message_error: .message}'

printf '%s\n' '--- remote refs ---'
git ls-remote --heads --tags "https://github.com/${repo}.git" 2>&1 |
  grep -E 'refs/(heads/(main|master|dev-main)|tags/)' | tail -80

printf '%s\n' '--- current dev-main tree paths ---'
curl -sS --fail --show-error \
  "https://api.github.com/repos/${repo}/git/trees/dev-main?recursive=1" |
  jq -r '.tree[]?.path' | grep -E '\.php$' | head -200

printf '%s\n' '--- current dev-main cloneAs references ---'
curl -sS --fail --show-error \
  "https://api.github.com/repos/${repo}/search/code?q=cloneAs+repo:${repo}" |
  jq -r '.items[]?.html_url' 2>/dev/null || true

Repository: VitexSoftware/multiflexi-cli

Length of output: 878


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
branch='main'

curl --fail --silent --show-error \
  "https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1" |
  jq -r '.tree[] | select(.type == "blob") | .path' |
  tee /tmp/multiflexi-core-php-paths.txt |
  grep -Ei 'run.?template|template|\.php$' |
  head -250

printf '%s\n' '--- candidate source excerpts ---'
while IFS= read -r path; do
  case "$path" in
    *[Rr]un*[Tt]emplate*.php|*runtemplate*.php|*RunTemplate*.php)
      url="https://raw.githubusercontent.com/${repo}/${branch}/${path}"
      body="$(curl --fail --silent --show-error "$url")"
      if printf '%s' "$body" | grep -qE 'cloneAs|class RunTemplate'; then
        printf '%s\n' "--- ${path} ---"
        printf '%s\n' "$body" | grep -n -C 20 -E 'cloneAs|class RunTemplate'
      fi
      ;;
  esac
done < /tmp/multiflexi-core-php-paths.txt

Repository: VitexSoftware/multiflexi-cli

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
branch='main'
tree_file=/tmp/multiflexi-core-tree.json

curl --fail --silent --show-error \
  "https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1" > "$tree_file"

printf '%s\n' '--- candidate paths ---'
jq -r '.tree[] | select(.type == "blob") | .path' "$tree_file" |
  grep -Ei 'run.?template|template|\.php$' |
  head -250

printf '%s\n' '--- candidate source excerpts ---'
jq -r '.tree[] | select(.type == "blob") | .path' "$tree_file" |
  while IFS= read -r path; do
    case "$path" in
      *[Rr]un*[Tt]emplate*.php|*runtemplate*.php|*RunTemplate*.php)
        url="https://raw.githubusercontent.com/${repo}/${branch}/${path}"
        body="$(curl --fail --silent --show-error "$url")"
        if printf '%s' "$body" | grep -qE 'cloneAs|class RunTemplate'; then
          printf '%s\n' "--- ${path} ---"
          printf '%s\n' "$body" | grep -n -C 20 -E 'cloneAs|class RunTemplate'
        fi
        ;;
    esac
  done

Repository: VitexSoftware/multiflexi-cli

Length of output: 12259


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
for ref in main v2.9.3; do
  file="/tmp/multiflexi-core-RunTemplate-${ref}.php"
  curl --fail --silent --show-error \
    "https://raw.githubusercontent.com/${repo}/${ref}/src/MultiFlexi/RunTemplate.php" > "$file"

  printf '%s\n' "--- ${ref}: clone-related methods and active assignments ---"
  grep -n -C 12 -Ei 'function[[:space:]]+[^ (]*clone|cloneAs|active|setDataValue|save' "$file" || true
done

printf '%s\n' '--- direct existence check ---'
for ref in main v2.9.3; do
  url="https://raw.githubusercontent.com/${repo}/${ref}/src/MultiFlexi/RunTemplate.php"
  if curl --fail --silent --show-error "$url" | grep -qE 'function[[:space:]]+cloneAs[[:space:]]*\('; then
    printf '%s: cloneAs present\n' "$ref"
  else
    printf '%s: cloneAs absent\n' "$ref"
  fi
done

Repository: VitexSoftware/multiflexi-cli

Length of output: 15773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
ref='main'
tree_file=/tmp/multiflexi-core-tree.json

printf '%s\n' '--- DBEngine clone-related methods ---'
curl --fail --silent --show-error \
  "https://raw.githubusercontent.com/${repo}/${ref}/src/MultiFlexi/DBEngine.php" |
  grep -n -C 20 -Ei 'function[[:space:]]+[^ (]*clone|cloneAs|clone|active|setDataValue' || true

printf '%s\n' '--- all upstream source references to cloneAs ---'
jq -r '.tree[] | select(.type == "blob" and (.path | startswith("src/")) and (.path | endswith(".php"))) | .path' "$tree_file" |
  while IFS= read -r path; do
    url="https://raw.githubusercontent.com/${repo}/${ref}/${path}"
    body="$(curl --fail --silent --show-error "$url")"
    if printf '%s' "$body" | grep -qEi 'cloneAs'; then
      printf '%s\n' "--- ${path} ---"
      printf '%s\n' "$body" | grep -n -C 15 -Ei 'cloneAs'
    fi
  done

Repository: VitexSoftware/multiflexi-cli

Length of output: 255


Align the multiflexi-core dependency before calling cloneAs(). composer.lock references nonexistent commit 80eefa9362998179f5c68374ec5dc77fd605cc57, and available upstream source contains no cloneAs() implementation. Pin a reachable revision that provides the API, or use a supported cloning API. Add an integration test for the inactive state and documented failure behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` at line 65, Before the cloneAs call
in the RunTemplate clone flow, align the multiflexi-core dependency to a
reachable revision that provides cloneAs, or replace it with the supported
cloning API. Update the lockfile accordingly, then add an integration test
covering the inactive state and the documented failure behavior.

Source: Coding guidelines

@Vitexus
Vitexus merged commit 7069f8b into main Aug 12, 2026
9 of 10 checks passed
@Vitexus
Vitexus deleted the fix/runtemplate-clone-disabled branch August 12, 2026 11:26
Vitexus added a commit to VitexSoftware/multiflexi-doc-en that referenced this pull request Aug 12, 2026
Covers the new CLI command (VitexSoftware/multiflexi-cli#56) which
mirrors the web UI's clone action and always creates the clone
disabled.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant