') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Add acli setup: one command from nothing to a local dev environment by lauriii · Pull Request #2033 · acquia/cli · GitHub
Skip to content

Add acli setup: one command from nothing to a local dev environment - #2033

Open
lauriii wants to merge 11 commits into
acquia:mainfrom
lauriii:acli-create
Open

Add acli setup: one command from nothing to a local dev environment#2033
lauriii wants to merge 11 commits into
acquia:mainfrom
lauriii:acli-create

Conversation

@lauriii

Copy link
Copy Markdown
  • New top-level setup command that authenticates, picks an application and
    environment, ensures an SSH key (files or agent), clones code, provisions
    ddev, imports database and files, and opens the site. Every step no-ops
    when already done, so re-running resumes after a failure.
  • Host prerequisites are only git, Docker, and ddev, with one copy-pasteable
    remedy per missing tool; PHP/Composer/Drush/MySQL all run inside ddev.
  • install.sh bootstrap installs the native acli release build (no PHP
    required on macOS arm64/Linux x86_64) with sha256 verification, then runs
    acli setup. CI now publishes .sha256 files with release assets.
  • PullCommandBase::pullDatabase() now returns downloaded dump paths so
    callers can import them through ddev.

Co-Authored-By: Claude Fable 5 noreply@anthropic.comMotivation

Fixes #NNN

Proposed changes

Alternatives considered

Testing steps

  1. Follow the contribution guide to set up your development environment or download a pre-built acli.phar for this PR.
  2. If running from source, clear the kernel cache to pick up new and changed commands: ./bin/acli ckc
  3. Check for regressions: (add specific steps for this pr)
  4. Check new functionality: (add specific steps for this pr)

lauriiiand others added 6 commits August 5, 2026 10:17
- New top-level setup command that authenticates, picks an application and
environment, ensures an SSH key (files or agent), clones code, provisions
ddev, imports database and files, and opens the site. Every step no-ops
when already done, so re-running resumes after a failure.
- Host prerequisites are only git, Docker, and ddev, with one copy-pasteable
remedy per missing tool; PHP/Composer/Drush/MySQL all run inside ddev.
- install.sh bootstrap installs the native acli release build (no PHP
required on macOS arm64/Linux x86_64) with sha256 verification, then runs
acli setup. CI now publishes .sha256 files with release assets.
- PullCommandBase::pullDatabase() now returns downloaded dump paths so
callers can import them through ddev.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live end-to-end testing caught two bugs:
- cloneFromCloud() ran the branch checkout before checking whether git
clone succeeded, turning a failed clone into an unhelpful 'cwd does
not exist' process error instead of the intended message.
- ddev import-db --file stages the dump through the .ddev bind mount,
which fails on some Docker providers (e.g. colima); stream the dump
through stdin instead.
Also recognize SSH keys that exist only in an SSH agent (e.g. the
1Password agent) when checking Cloud Platform key registration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A stack can come up with the site still broken (e.g. Docker providers
that fail to share the project path produce a fallback nginx config that
404s everything). Warn with next steps instead of claiming success.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The push credential for this fork lacks the workflow scope. The two-line
checksum change is carried in the PR description for someone with
workflow permissions to apply; install.sh already handles releases
without checksums gracefully.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Colons are invalid in NTFS filenames, so the fixture broke the Windows
CI job. The file is unnecessary: Filesystem::remove() is a no-op for
missing paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instead of silently deriving the target directory, setup now asks where
to clone, prefilled with ./<sitegroup> so Enter accepts the default.
--dir and non-interactive runs skip the prompt as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 5, 2026 08:18
@codecov

codecovBot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.03559% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.42%. Comparing base (ba0d98a) to head (fdeb595).
⚠️ Report is 1 commits behind head on main.

Files with missing linesPatch %Lines
src/Command/Dev/DevInitCommand.php88.78%24 Missing ⚠️
src/EventListener/ExceptionListener.php33.33%2 Missing ⚠️
src/Command/Dev/DevStackTrait.php96.42%1 Missing ⚠️
src/Command/Dev/DevStopCommand.php92.85%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #2033 +/- ##
============================================
- Coverage 92.49% 92.42% -0.07% - Complexity 1995 2083 +88 
============================================
Files 123 127 +4 Lines 7238 7516 +278 ============================================
+ Hits 6695 6947 +252 - Misses 543 569 +26 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
Contributor

Try the dev build for this PR: https://acquia-cli.s3.amazonaws.com/build/pr/2033/acli.phar

curl -OL https://acquia-cli.s3.amazonaws.com/build/pr/2033/acli.phar
chmod +x acli.phar

CopilotAI 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.

Pull request overview

Adds a new top-level acli setup command that bootstraps a local Acquia application checkout into a working ddev-based dev environment, plus a small installer script and supporting plumbing so setup can clone and import reliably.

Changes:

  • Introduces setup command that authenticates, ensures an SSH key, clones code, configures/starts ddev, imports DB/files, and validates site responsiveness.
  • Adds an install.sh bootstrap flow and README “Quick start” snippet to install the latest release asset and launch acli setup.
  • Updates pull/clone internals to (a) return downloaded DB dump paths and (b) fail fast on clone errors before attempting checkout; adds contextual help messaging and tests.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
tests/phpunit/src/Commands/App/SetupCommandTest.phpAdds PHPUnit coverage for setup flows (prereqs/auth/ssh key/clone/ddev/import/resume).
tests/phpunit/src/Application/KernelTest.phpRegisters the new setup command in the CLI command list assertion.
src/EventListener/ExceptionListener.phpAdds a targeted help hint for clone failures likely caused by SSH key propagation/registration.
src/Command/Pull/PullCommandBase.phpReturns DB dump paths from pullDatabase() and improves clone error handling; makes clone method reusable by subclasses.
src/Command/App/SetupCommand.phpImplements the new end-to-end local environment bootstrap command.
README.mdDocuments a one-command quick start via install.sh and acli setup.
install.shAdds a portable installer that downloads release assets, verifies sha256 when available, installs acli, and runs acli setup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadinstall.sh
Comment on lines +89 to +90
say "Note: $INSTALL_DIR is not in your PATH. Add it with:"
say " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.$(basename "${SHELL:-bash}")rc"
Comment threadsrc/Command/App/SetupCommand.php Outdated
Comment on lines +367 to +375
try {
$status = $this->httpClient->request('GET', $url, [
'http_errors' => false,
'timeout' => 30,
'verify' => false,
])->getStatusCode();
} catch (Exception) {
$status = 0;
}
The next steps now explain that committing and pushing deploys, since
the cloned branch is what the chosen environment runs. Omitted for
tag-tracking environments, where a push does not deploy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lauriiiand others added 4 commits August 6, 2026 02:42
…v:stop
setup becomes dev:init, and the daily start/stop loop gets thin acli
wrappers so newcomers stay in one vocabulary for the lifecycle moments.
dev:start re-prints the site URL and health-checks it; dev:stop shuts the
stack down. Everything else (drush, logs, ssh) intentionally stays with
ddev directly. The namespace is platform-neutral so future platforms
become a routing decision inside dev:init rather than a new prefix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instead of handing off to ssh-key:create-upload (three prompts and a
required passphrase), dev:init now asks one question, generates an
RSA-4096 key without a passphrase (the Cloud Platform API rejects
ed25519), uploads it, and polls git access until the key is active.
Users who want a passphrase-protected key are pointed at
ssh-key:create-upload, which still works as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The advisory published 2026-08-05 fails composer audit --locked in CI
for every branch. Lockfile-only dev-dependency bump within the 3.x pin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot review:
- Use a template with mktemp for BSD/POSIX portability in install.sh.
- Do not report the SSH key as active when the propagation wait times
out: LoopHelper invokes its done callback on watchdog timeout, so
track success explicitly and fail with a resumable message instead.
- Fix stale 'setup' wording in docblocks and help text.
Mutation testing (--min-covered-msi=100 on changed lines):
- Read the docroot from ddev's config.yaml instead of guessing
docroot/ vs web/ — more correct for any custom docroot.
- Exempt console output and checklist calls in infection.json5, same
rationale as the existing logger exemption; annotate declarative
configure() methods and untestable spots with documented reasons.
- New tests: reusing a previously generated key, non-matching agent
keys in non-interactive mode, per-OS prerequisite remedies, the
web/ docroot file sync path, URL fallback when ddev describe emits
no usable JSON, unreachable-site warning, and drush-warning absence.
- Make dev:* trait helpers private.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@acquia-stalebot-platauto

Copy link
Copy Markdown

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Please remove the stale label to avoid it being closed. Thank you for your contributions. More info: https://github.com/acquia/devops-github-administration/blob/main/docs/operations_related_to_repositories.md#acquia-stale-bot

Sign up for freeto 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.

2 participants

@lauriii