Skip to content

[3.x] New publish command#2500

Draft
emmadesilva wants to merge 18 commits into
v3-devfrom
v3/new-publish-command
Draft

[3.x] New publish command#2500
emmadesilva wants to merge 18 commits into
v3-devfrom
v3/new-publish-command

Conversation

@emmadesilva

Copy link
Copy Markdown
Member

No description provided.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.42342% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.65%. Comparing base (078d7c9) to head (6f48418).

Files with missing lines Patch % Lines
...s/framework/src/Console/Helpers/PagesPublisher.php 97.40% 5 Missing ⚠️
...s/framework/src/Console/Helpers/ViewsPublisher.php 98.13% 2 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##              v3-dev    #2500      +/-   ##
=============================================
- Coverage     100.00%   99.65%   -0.35%     
- Complexity      1608     1751     +143     
=============================================
  Files            168      175       +7     
  Lines           4039     4372     +333     
=============================================
+ Hits            4039     4357     +318     
- Misses             0       15      +15     

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

emmadesilva and others added 16 commits July 4, 2026 03:47
Introduces the shared, pure decision primitive for the new publish command:
given a source and destination path, it returns copy (missing), skip
(unchanged), or blocked (user-modified). Comparison is EOL-agnostic via
unixsum so line-ending-only differences (e.g. CRLF checkouts) count as
unchanged rather than modified. No console/view/page knowledge; no checksum
manifest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Introduces the data model for starter pages used by the new publish command:
an immutable PublishablePage value object (key, label, description, source,
defaultTarget, alternativeTargets, allowCustomTarget) and the PublishablePages
registry (all/get/register) seeded with the default catalog — welcome, posts,
blank, and 404. Pages get a value object + registry (unlike the fixed view
file-maps) because a page can have multiple valid destinations, carries display
metadata, and the registry is an extension point for Hyde Cloud and plugins.

Source paths are stored framework-relative for resolution via Hyde::vendorPath()
at publish time; destination resolution and publishing land in a later step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Per review decision: `blank` is a general-purpose empty starter you drop
anywhere rather than a third homepage variant, so it declares no default
target. Makes PublishablePage::$defaultTarget nullable and sets blank's to
null; destination resolution (Step 5) will always prompt interactively and
require --to non-interactively. This also removes the welcome/blank collision
on _pages/index.blade.php that the default catalog would otherwise create.

Updates spec 5.1/5.2/5.4 to match so the acceptance sweep stays consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Introduces the flag-driven publish command shell that routes to the views and
pages flows (both stubbed here, filled in by Steps 4-5). It owns the full flag
surface (--layouts --components --all --page[=NAME] --to=PATH --force) and all
guardrails:

- Raw tag/provider/config publishing is redirected to vendor:publish. These are
  deliberately not declared as options (which would advertise them in --help,
  the exact raw-publishing surface this command exists to hide); instead run()
  intercepts the raw input before Symfony's strict bind. Only those three tokens
  are short-circuited, so a genuine typo like --layout still hits Symfony's
  native "unknown option" error rather than being swallowed.
- --layouts and --components are mutually exclusive.
- --to is only valid alongside --page.
- Non-interactive with no actionable flag fails with a usage hint before any
  prompt is attempted; the interactive wizard (Views / A starter page / Cancel)
  routes to the stub handlers, with Cancel exiting cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Replace the publishViews() stub with a real ViewsPublisher (§4, §7):

- Reads the two declared groups (layouts, components) via ViewPublishGroup;
  --layouts/--components prefilter the offered set, --all skips the picker,
  and a non-interactive scoped run behaves exactly like adding --all.
- Adds a minimal shared InteractiveMultiselect helper: a grouped multi-select
  with an "All views" sentinel (selecting it means everything regardless of
  other checkbox state) and group-prefixed labels (layouts/app.blade.php).
- Decides every file's outcome first (OverwritePolicy: copy/skip/blocked),
  resolves modified-file conflicts second (interactive Skip/Overwrite/Cancel
  or --force; non-interactive without --force is a hard §7 error), and writes
  last, so Cancel never leaves a half-published tree.
- Cardinality-aware output that reports the real breakdown: "Published all N"
  prints only when the entire offered set was genuinely copied; mixed runs
  report copied / already-current / left-modified (with a --force hint).

Re-points the four Step 3 views-routing tests off the removed stub string to
assert real views behavior, adds a dedicated views-flow test suite, and notes
the mixed-run reporting rule in §4 of the spec.

Additional commits:

- Make the publish multiselect "All" sentinel row optional

Add a nullable $allLabel to InteractiveMultiselect::select() so callers can omit the
"select all" row entirely. The views picker keeps its "All views" affordance; the pages
picker (Step 5) passes no label, since "publish all starter pages at once" is never a
sensible selection and would only trip destination resolution and conflict detection.

This is a backward-compatible parameter on the shared helper, not a fork — both callers
depend on the same picker.

- Simplify publish multiselect all sentinel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Replace the publishPage() stub with a real PagesPublisher backed by the PublishablePages
registry (§5):

- Selection: --page=NAME publishes one page directly; a bare --page or the wizard opens the
  interactive multi-select picker.
- Destination resolution (§5.4): --to → non-interactive default → interactive prompt
  (default / alternative / custom path) → default. A page with no default (blank) fails
  helpfully non-interactively, pointing to --to.
- --to validation: must resolve under _pages/ and end in .blade.php.
- --to is only valid for a single named page; a bare --page (multi-select) with --to is
  rejected. Documented as a new line in spec §5.4.
- Conflict detection (§5.6): two pages resolving to the same target fail before any write.
- Interactive confirm (§5.5): select → resolve → "Ready to publish… Proceed?".
- Overwrite policy (§7): reuses OverwritePolicy exactly as the views flow does
  (missing→copy, identical→skip, modified→confirm-or-force).
- Optional rebuild (§5.7): offered interactively only, defaulting to NO. Implemented inline
  rather than via AsksToRebuildSite, which defaults to YES — a why-comment guards against a
  future consolidation reintroducing the yes-default.

Registry lookups compare ->key (never array access) so the string '404' key survives PHP's
numeric-key coercion. The Step 3 routing tests that asserted the old stub are updated to
assert the real, non-destructive routing behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Destination resolution UX (§5.4): only prompt interactively when a page is genuinely
ambiguous — it has alternative targets or no default. A page whose default is its one
sensible destination (welcome, 404) publishes in a single frictionless step instead of
asking "Where should this go?" for a near-certain answer. allowCustomTarget now governs
only whether a "Custom path…" entry is offered and whether --to is accepted, not whether
the prompt appears; custom placement of an otherwise-unambiguous page is reached via --to.
Consequently --to is rejected for a page that disallows custom targets (404).

Polish:
- The overwrite-conflict "Cancel" choice now prints "Cancelled. No pages were published."
  instead of exiting silently, matching the views flow and the "Proceed? no" path.
- The destination-conflict message uses "both target" for a pair and "all target" for
  three or more colliding pages.
- Added tests: --to rejected for 404, the picker round-tripping the numeric '404' key
  (the coerced-int-key path the named lookup can't reach), and the pages picker omitting
  the "All" row that the views picker offers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Add a test proving the two --to rejections cannot disagree. Bare --page + --to is caught by
the top-level "one path can't serve several pages" guard (message A) before selectPages()
runs, so the per-page allowCustomTarget rejection (message B) can never pre-empt it. The
test runs interactively — it would hang on an unanswered picker prompt if the picker were
reached — and asserts message A wins and message B is absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Add a single `hyde-config` publish group on ConfigurationServiceProvider
that publishes exactly the six Hyde-owned config files (hyde, docs,
markdown, view, cache, commands) and not torchlight.php, per spec §6.

The legacy `configs` / `hyde-configs` / `support-configs` tags are left
in place; the deprecated publish:configs command still relies on them
until Step 7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Per spec §8, publish:views, publish:configs, and publish:homepage become
thin delegators that print a one-line deprecation notice and forward to the
new surface:

- publish:views [group] -> publish --layouts / --components (no group -> --all,
  keeping legacy non-interactive scripts non-interactive)
- publish:configs -> vendor:publish --tag=hyde-config
- publish:homepage [template] -> publish --page=[template], forwarding --force

The old command classes stay registered and keep working through v3; target
removal in v4. Their tests are rewritten to assert the notice and delegation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Rewrite the publishing docs around php hyde publish (views + --page) and
php hyde vendor:publish --tag=hyde-config for config. Fix the nonexistent
publish:components reference in advanced-markdown.md, and update remaining
publish:views/publish:configs/publish:homepage references throughout the
primary docs. The deprecated aliases now appear only in a migration note
in the console commands reference (historical release notes left as-is).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Gemini <218195315+gemini-cli@users.noreply.github.com>
Co-Authored-By: Codex <codex@openai.com>
Fill the coverage gaps flagged on PagesPublisher and ViewsPublisher by
testing the feature paths they exercise, not lines for their own sake:

Pages (§5, §7):
- interactive overwrite-conflict prompt: overwrite / skip / cancel
  (the §7 interactive flow was tested for views but not for pages)
- mixed run reporting published pages alongside already-current ones,
  with pluralized cardinality
- three-or-more pages colliding on one target ("all target" wording)
- accepting the §5.7 rebuild offer actually runs the build
- --to path traversal (..) rejected

Views (§4):
- mixed run reporting copied views alongside already-current ones

The only lines left uncovered are unreachable defensive guards behind
`required` multi-selects (empty-selection is rejected by the prompt
before the guard) and joinLabels' <2 branch — not feature paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@emmadesilva emmadesilva force-pushed the v3/new-publish-command branch from 1b5066d to 6f48418 Compare July 4, 2026 17:58
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.

2 participants