Fix redirect-hop links in why-choose-pulumi-over-terraform blog post - #21145
Conversation
Rewrite three occurrences of https://www.pulumi.com/docs/concepts/stacks/ (a 301 redirect) to the direct 200 target https://www.pulumi.com/docs/iac/concepts/stacks/, removing an unnecessary redirect hop on the Pulumi vs Terraform comparison page.
Pre-merge Review — Last updated 2026-08-26T21:23:10ZTip Summary: acd3315 is the one-byte fix for the only blocker: the trailing newline is back, verified at the byte level rather than taken from the diff marker — the file's last two bytes are now Review confidence:
Investigation log
✏️ 1 one-click style suggestion is posted inline — apply it from the Files changed tab. 🔍 Verification trail24 claims extracted · 18 verified · 2 unverifiable · 0 contradicted · 1 detector findings
📊 Editorial balanceSingle-subject post; balance check N/A. 🚨 Outstanding in this PRNo outstanding findings in this PR.
|
…sting on this PR The review bot's pre-existing finding on this PR noted that lines 33, 46, and 48 of why-choose-pulumi-over-terraform/index.md carry four more redirect hops of the same class this PR removes: aliases, protect, deleteBeforeReplace, and replaceOnChanges all linked to /docs/iac/concepts/options/<name>/, which each 301 (verified live) to /docs/iac/concepts/resources/options/<name>/ per the target page's own aliases: frontmatter. retainOnDelete on the same lines already used the canonical path, which is what made the others stand out. While fixing those, found the same hop repeats twice more in this file (L106, L143) that weren't called out in the original finding. Fixed all 12 occurrences with the same s/options/resources\/options/ substitution, verified with a diff of before/after occurrence counts (10 hop links, now 0; 12 canonical links, up from 2).
|
Folded this in — pushed 0bf0d7b. All 12 occurrences of @claude #update-review |
…output Addresses both Outstanding findings and both Low-confidence findings from the review bot on this PR. Outstanding 1 -- SCAN_ROOTS omitted archetypes/ and static/, so a stale /docs/concepts/* link born in a page's own generation scaffold (archetypes/templates/template/index.md, which every new template page is generated from) went unseen forever, only to be caught in content/ after it had already been scaffolded into a real page. Added archetypes, static, and scripts to SCAN_ROOTS. Including scripts pulled in one occurrence, scripts/search/rank.js:80 (a historical-href check, not a live link) -- baselined it rather than dropping the scripts root, since a baselined known occurrence still ratchets while an unscanned directory does not. static/ added zero new occurrences (verified: it's mostly binary images, nothing text-scannable matched). Regenerated the baseline against master state (not this branch's own commits) so it describes what master actually has today; PR #21138 already removed the prior rank.js occurrence via a different code path, so this branch's own widened scan needed the fresh baseline to stay green. Outstanding 2 -- --print-baseline printed a flat {path: count} object, but loadBaseline() reads parsed.files || {}. Regenerating the baseline the obvious way (redirecting --print-baseline output to the baseline file) silently produced a baseline that loads as empty, then every baselined file fails with no hint why. Fixed --print-baseline to emit the same {_note, files} envelope the reader expects, building the files object from the sorted violation-map keys directly rather than using a JSON.stringify replacer keyed off Object.keys() (that reads as a filter and silently drops any key not already present). Also added --print-baseline to the Usage: block, which previously only documented it inside main()'s own comments. Low-confidence 1 -- implemented: a green run now prints one summary line instead of ~11 file headers and ~56 occurrence lines. Full detail only prints when the check actually fails. Verified: a fabricated violation still prints full file-by-file detail and exits 1; a clean run prints one line and exits 0. Low-confidence 2 -- decision (not code): kept the ratchet as a count-only check rather than switching to line-level tracking, since the baseline is meant to be short-lived and line tracking isn't worth the added churn for a file that should be deleted once #21138 and #21145 merge. Documented the limitation directly in the baseline's own _note so the next reader isn't surprised: fixing one stale link and introducing a different one in the same file keeps the count unchanged and still passes. Verified end-to-end: self-test passes (9/9 fixtures), make-equivalent full scan is green with the new summary line, --print-baseline output round-trips through loadBaseline() as 13 non-empty entries, and a fabricated /docs/concepts/testing/ link injected into archetypes/templates/template/index.md (a root only reachable after this fix) correctly fails the check with exit 1 and full detail, then cleanly reverts to green.
|
Your site preview for commit acd3315 is ready! 🎉 http://www-testing-pulumi-docs-origin-pr-21145-acd3315c.s3-website.us-west-2.amazonaws.com Changed pages: |
|
🤖 Review updated on @workprentice[bot]'s request. |
The review's own redirect-hop sweep found eight more hops in this file outside the resource-options set already fixed, in four families: /docs/iac/adopting-pulumi/import/ (4x), /docs/deployments/deployments/drift/ (2x), /docs/esc/guides/integrate-with-pulumi-iac/ (1x), and /docs/iac/adopting-pulumi/migrating-to-pulumi/from-terraform/ (1x). Each target's aliases: frontmatter was independently re-verified against origin/master (not just taken on the review's word), and each new canonical URL curl-checked live at a direct 200 before editing. Since this PR's stated purpose is removing redirect hops from this specific file, and the file is already open here, closing out every remaining hop in one pass beats leaving four more for a future sweep to rediscover.
|
Addressed both points from this review pass. Wording nit: fixed the "12 occurrences" claim in the description — it's 10 links rewritten (the other 2 Remaining hop families: folded in all four ( @claude #update-review |
There was a problem hiding this comment.
🧹 Optional style suggestions from the pre-merge review — apply or dismiss; none of them block.
Generated by Claude Code
| Infrastructure drifts when something changes outside your IaC tool: a hotfix in the console, another controller, or a manual break-glass change. Pulumi gives you first-class tools to find and fix it instead of leaving it to chance. [`pulumi refresh`](https://www.pulumi.com/docs/iac/cli/commands/pulumi_refresh/) reconciles your state with what is actually running in the cloud, and `pulumi preview --diff` shows what the next update would change. | ||
|
|
||
| For teams that want this continuously, Pulumi Cloud adds [scheduled drift detection and remediation](https://www.pulumi.com/docs/deployments/deployments/drift/) that runs on a configurable cadence and can automatically remediate drift when it is detected. That turns finding and correcting out-of-band changes into a managed workflow rather than something a person has to remember to check. | ||
| For teams that want this continuously, Pulumi Cloud adds [scheduled drift detection and remediation](https://www.pulumi.com/docs/deployments/concepts/drift/) that runs on a configurable cadence and can automatically remediate drift when it is detected. That turns finding and correcting out-of-band changes into a managed workflow rather than something a person has to remember to check. |
There was a problem hiding this comment.
[style] wordiness — drops filler; meaning unchanged
| For teams that want this continuously, Pulumi Cloud adds [scheduled drift detection and remediation](https://www.pulumi.com/docs/deployments/concepts/drift/) that runs on a configurable cadence and can automatically remediate drift when it is detected. That turns finding and correcting out-of-band changes into a managed workflow rather than something a person has to remember to check. | |
| For teams that want this continuously, Pulumi Cloud adds [scheduled drift detection and remediation](https://www.pulumi.com/docs/deployments/concepts/drift/) that runs on a configurable cadence and can automatically remediate drift when detected. That turns finding and correcting out-of-band changes into a managed workflow rather than something a person has to remember to check. |
|
🤖 Review updated on @workprentice[bot]'s request. |
|
🤖 Review auto-refreshed — the latest push only touched lines with outstanding findings. |
…output Addresses both Outstanding findings and both Low-confidence findings from the review bot on this PR. Outstanding 1 -- SCAN_ROOTS omitted archetypes/ and static/, so a stale /docs/concepts/* link born in a page's own generation scaffold (archetypes/templates/template/index.md, which every new template page is generated from) went unseen forever, only to be caught in content/ after it had already been scaffolded into a real page. Added archetypes, static, and scripts to SCAN_ROOTS. Including scripts pulled in one occurrence, scripts/search/rank.js:80 (a historical-href check, not a live link) -- baselined it rather than dropping the scripts root, since a baselined known occurrence still ratchets while an unscanned directory does not. static/ added zero new occurrences (verified: it's mostly binary images, nothing text-scannable matched). Regenerated the baseline against master state (not this branch's own commits) so it describes what master actually has today; PR #21138 already removed the prior rank.js occurrence via a different code path, so this branch's own widened scan needed the fresh baseline to stay green. Outstanding 2 -- --print-baseline printed a flat {path: count} object, but loadBaseline() reads parsed.files || {}. Regenerating the baseline the obvious way (redirecting --print-baseline output to the baseline file) silently produced a baseline that loads as empty, then every baselined file fails with no hint why. Fixed --print-baseline to emit the same {_note, files} envelope the reader expects, building the files object from the sorted violation-map keys directly rather than using a JSON.stringify replacer keyed off Object.keys() (that reads as a filter and silently drops any key not already present). Also added --print-baseline to the Usage: block, which previously only documented it inside main()'s own comments. Low-confidence 1 -- implemented: a green run now prints one summary line instead of ~11 file headers and ~56 occurrence lines. Full detail only prints when the check actually fails. Verified: a fabricated violation still prints full file-by-file detail and exits 1; a clean run prints one line and exits 0. Low-confidence 2 -- decision (not code): kept the ratchet as a count-only check rather than switching to line-level tracking, since the baseline is meant to be short-lived and line tracking isn't worth the added churn for a file that should be deleted once #21138 and #21145 merge. Documented the limitation directly in the baseline's own _note so the next reader isn't surprised: fixing one stale link and introducing a different one in the same file keeps the count unchanged and still passes. Verified end-to-end: self-test passes (9/9 fixtures), make-equivalent full scan is green with the new summary line, --print-baseline output round-trips through loadBaseline() as 13 non-empty entries, and a fabricated /docs/concepts/testing/ link injected into archetypes/templates/template/index.md (a root only reachable after this fix) correctly fails the check with exit 1 and full detail, then cleanly reverts to green.
PR #21138 and PR #21145 (the cleanup pass this guard exists to protect) both merged today. Every one of the 12 baselined files now has 0 real occurrences of an internal /docs/concepts/* link on master, so the 57-occurrence allowance list was pure dead weight: a future PR could have introduced up to that many brand-new stale links per file and the guard would stay green and silent, which is exactly the regression it exists to catch. Rebased onto current master and reset the baseline to {}, updating its _note and this script's own doc comments to stop describing the two PRs as still open. Verified against the merged tree: a full scan finds 0 occurrences in all 12 previously-baselined files, and a fresh --print-baseline confirms an empty {_note, files: {}} envelope is the correct state.
…/options/* redirect hops (#21155) * Fix internal redirect-hop and broken links in the /docs/using-pulumi/ and /docs/iac/concepts/options/ hop classes Three /docs/using-pulumi/ link defects, each independently verified: - debugging-providers.md carried a stray `/docs/using-pulumi/pulumi-packages/` alias that collides with the same alias already declared on content/docs/iac/concepts/packages/_index.md:14 (the debugging page wins the collision, so the link never reached the packages index). Looks like a truncated copy-paste amid a run of `.../debugging-provider-packages/` aliases on the same page. Deleted the stray entry. - import/_index.md:501 linked "component package" to /docs/using-pulumi/pulumi-packages/#types-of-pulumi-packages, an anchor that exists on neither the debugging page nor the packages index (checked both). Repointed to the packages concepts page itself, without the nonexistent fragment. - opentofu.md:148 hopped via scripts/redirects/general-broken-links-redirects.txt:7 to /docs/iac/guides/migration/, whose #conversion anchor does exist (confirmed at guides/migration/_index.md:76). Pointed directly at the canonical URL. Also folded in the remaining /docs/iac/concepts/options/* redirect-hop occurrences outside the blog file already covered by PR #21145: 8 more files with live body links to the old /docs/iac/concepts/options/<name>/ path, each verified live (301 -> /docs/iac/concepts/resources/options/<name>/, confirmed via curl) and rewritten to the canonical /docs/iac/concepts/resources/options/<name>/ path. aliases: frontmatter entries under both prefixes were left untouched -- they are legitimate redirect stubs, not violations. * Add missing trailing slash on two options/parent.md links flagged by review --------- Co-authored-by: workprentice <257153108+workprentice@users.noreply.github.com>
This closes out the last survivor from the /docs/concepts/* redirect-hop cleanup (see card 52cf77c0 and PRs #21072, #21138): links in the Pulumi vs. Terraform blog post that still pointed at old, 301-redirected URLs.
What changed
Stacks links (original scope). Rewrote all three occurrences of
https://www.pulumi.com/docs/concepts/stacks/(lines 33, 49, 147 incontent/blog/why-choose-pulumi-over-terraform/index.md) to the direct, live targethttps://www.pulumi.com/docs/iac/concepts/stacks/.Resource-options links (folded in from the pre-existing review finding). The same file carried a second class of hop on the lines this PR already touches:
aliases,protect,deleteBeforeReplace, andreplaceOnChangesall linked to/docs/iac/concepts/options/<name>/, which each 301 to/docs/iac/concepts/resources/options/<name>/per the target page's ownaliases:frontmatter.retainOnDeleteon the same lines already used the canonical path, which is what made the others stand out. The reviewer flagged this on lines 33, 46, and 48; while fixing those I found the same hop repeats twice more in the file (L106, L143) that weren't in the original finding. Rewrote 10 links total from the old path to the canonical one; the other 2 occurrences ofretainOnDeletealready pointed at the canonical path before this push, which is what made the other ten stand out in the first place.No prose was reflowed anywhere in this PR; every change is a pure link-target substitution.
Verified live immediately before this PR:
https://www.pulumi.com/docs/concepts/stacks/-> 301https://www.pulumi.com/docs/iac/concepts/stacks/-> 200 (direct, no further hop)https://www.pulumi.com/docs/iac/concepts/options/{aliases,protect,deletebeforereplace,replaceonchanges}/-> 301 eachhttps://www.pulumi.com/docs/iac/concepts/resources/options/{aliases,protect,deletebeforereplace,replaceonchanges}/-> 200 each (direct, no further hop)Why this was deferred, and why it's safe now
This file was excluded from the earlier cleanup passes because two open PRs (#21062, #21066) also touch it, and the working assumption was that editing it risked a merge conflict. That assumption didn't hold up under a direct check:
@@ -241,7 +241,7 @@, nowhere near the edited lines.@@ -1,7 +1,7 @@and@@ -59,7 +59,7 @@, both well outside git's 3-line merge context around the edited lines.To be certain rather than just line-counting, I ran
git merge-tree --write-treefor this branch against both PR branches (fetched aspull/21062/headandpull/21066/head). Both produced a clean merge tree with no conflict markers or messages:Both #21062 and #21066 have been open and untouched for several days, so this fix should not continue to wait on them.
Why this page matters
why-choose-pulumi-over-terraformis thepulumi vs terraformcomparison page — our highest-intent Tier 1 keyword (~720/mo) and a page LLM answer engines cite heavily for IaC comparison queries. Redirect hops on it waste crawl budget and dilute link equity on the page where that costs the most.Verification
node ./scripts/lint/lint-markdown.js content/blog/why-choose-pulumi-over-terraform/index.md-> 0 errorsgrep -c '/docs/iac/concepts/options/' index.mdbefore/after: 10 -> 0;grep -c '/docs/iac/concepts/resources/options/'before/after: 2 -> 12Closes the deferred item tracked on the Marketing Content Calendar board.
🧠 This PR was created by workprentice.
Remaining redirect hops (closed out)
The review's own sweep of this file after the resource-options fix found four more hop families outside that set, all pre-existing in the post before this PR touched it:
/docs/iac/adopting-pulumi/import//docs/iac/guides/migration/import//docs/deployments/deployments/drift//docs/deployments/concepts/drift//docs/esc/guides/integrate-with-pulumi-iac//docs/esc/guides/pulumi-iac//docs/iac/adopting-pulumi/migrating-to-pulumi/from-terraform//docs/iac/guides/migration/migrating-to-pulumi/from-terraform/Each mapping was independently re-verified against
origin/master'saliases:frontmatter (not taken on the review's word alone), and each new URL was curl-checked live at a direct200before editing. Since this PR's stated purpose is removing redirect hops from this file and it was already open here, closing out every remaining hop in one pass beats leaving four more for a future sweep to rediscover. With this push,grep -c '/docs/concepts/\|/docs/iac/concepts/options/\|/docs/iac/adopting-pulumi/\|/docs/deployments/deployments/\|/docs/esc/guides/integrate-with-pulumi-iac/'against the file returns 0.