Add new tutorial on burning down tech debt (#58552)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com> Co-authored-by: Vanessa <vgrl@github.com>
This commit is contained in:
@@ -21,6 +21,7 @@ children:
|
|||||||
- /explore-pull-requests
|
- /explore-pull-requests
|
||||||
- /write-tests
|
- /write-tests
|
||||||
- /refactor-code
|
- /refactor-code
|
||||||
|
- /reduce-technical-debt
|
||||||
- /review-ai-generated-code
|
- /review-ai-generated-code
|
||||||
- /learn-a-new-language
|
- /learn-a-new-language
|
||||||
- /modernize-legacy-code
|
- /modernize-legacy-code
|
||||||
|
|||||||
381
content/copilot/tutorials/reduce-technical-debt.md
Normal file
381
content/copilot/tutorials/reduce-technical-debt.md
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
---
|
||||||
|
title: Using GitHub Copilot to reduce technical debt
|
||||||
|
shortTitle: Reduce technical debt
|
||||||
|
intro: 'Use {% data variables.product.prodname_copilot_short %} to automate refactoring and maintenance tasks, freeing your team to focus on feature development.'
|
||||||
|
versions:
|
||||||
|
feature: copilot
|
||||||
|
topics:
|
||||||
|
- Copilot
|
||||||
|
contentType: tutorials
|
||||||
|
category:
|
||||||
|
- Burn down tech debt
|
||||||
|
- Author and optimize with Copilot
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Technical debt accumulates in every codebase: duplicate code, missing tests, outdated dependencies, and inconsistent patterns. These issues can accumulate because feature development is typically given a higher priority. This tutorial explains how you can use {% data variables.product.prodname_copilot %} to tackle technical debt systematically, without sacrificing feature velocity.
|
||||||
|
|
||||||
|
### Who this tutorial is for
|
||||||
|
|
||||||
|
This tutorial is designed to help engineering teams and technical leads reduce technical debt while maintaining the pace at which new features are delivered. You should have:
|
||||||
|
|
||||||
|
* A {% data variables.product.prodname_copilot_short %} subscription with access to {% data variables.copilot.copilot_coding_agent %}
|
||||||
|
* Admin access to at least one repository
|
||||||
|
* Familiarity with your team's development workflow
|
||||||
|
|
||||||
|
### What you'll accomplish
|
||||||
|
|
||||||
|
By the end of this tutorial, you'll have learned about:
|
||||||
|
|
||||||
|
* Using {% data variables.product.prodname_copilot_short %} to implement in-the-moment fixes
|
||||||
|
* Leveraging {% data variables.copilot.copilot_coding_agent %} for large-scale cleanup tasks
|
||||||
|
* Creating custom instructions to align {% data variables.product.prodname_copilot_short %} with your team's standards
|
||||||
|
* Measuring the impact of {% data variables.product.prodname_copilot_short %} on your technical debt
|
||||||
|
|
||||||
|
## Understanding the technical debt problem
|
||||||
|
|
||||||
|
Before starting to reduce the technical debt in a codebase, you should take some time to identify the types of technical debt your team faces most often.
|
||||||
|
|
||||||
|
Common types of technical debt include:
|
||||||
|
|
||||||
|
* **Code duplication** - The same logic implemented in multiple places
|
||||||
|
* **Missing tests** - Features without adequate test coverage
|
||||||
|
* **Outdated dependencies** - Libraries several versions behind current releases
|
||||||
|
* **Inconsistent patterns** - Different approaches to the same problem across your codebase
|
||||||
|
* **Legacy code** - Old code that works but doesn't follow current standards
|
||||||
|
|
||||||
|
The cost of technical debt compounds over time:
|
||||||
|
|
||||||
|
* Senior engineers spend time on routine updates instead of architecture design
|
||||||
|
* Code reviews become longer as reviewers debate inconsistent patterns
|
||||||
|
* New developers take longer to onboard due to confusing code organization
|
||||||
|
* Deployment risk increases as outdated dependencies accumulate vulnerabilities
|
||||||
|
|
||||||
|
## Using {% data variables.product.prodname_copilot_short %} in your IDE for in-the-moment fixes
|
||||||
|
|
||||||
|
The best way to avoid technical debt accumulating in your codebase is to prevent it getting into the codebase in the first place.
|
||||||
|
|
||||||
|
When you encounter technical debt during development, fix it immediately using {% data variables.product.prodname_copilot_short %} in your IDE.
|
||||||
|
|
||||||
|
### Quick refactoring workflow
|
||||||
|
|
||||||
|
1. While working in your IDE, highlight code that needs improvement.
|
||||||
|
1. Open {% data variables.copilot.copilot_chat_short %} in the IDE.
|
||||||
|
1. Ask {% data variables.product.prodname_copilot_short %} to refactor the code. For example:
|
||||||
|
|
||||||
|
* `Extract this into a reusable helper and add error handling`
|
||||||
|
* `Standardize this logging format to match our pattern`
|
||||||
|
* `Add null checks for all optional parameters`
|
||||||
|
* `Replace this deprecated API call with the current version` <!-- markdownlint-disable-line GHD046 -->
|
||||||
|
|
||||||
|
1. Review the suggested changes.
|
||||||
|
1. Accept the changes or ask {% data variables.product.prodname_copilot_short %} to modify its approach.
|
||||||
|
1. Run your tests to verify the changes work correctly.
|
||||||
|
|
||||||
|
### Example: Standardizing error handling
|
||||||
|
|
||||||
|
If you find inconsistent error handling—for example:
|
||||||
|
|
||||||
|
```javascript id="err-handling"
|
||||||
|
// Highlight this code
|
||||||
|
try {
|
||||||
|
await fetchData();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ask {% data variables.product.prodname_copilot_short %} to improve the code—for example:
|
||||||
|
|
||||||
|
```copilot prompt copy ref="err-handling"
|
||||||
|
Refactor this to use structured logging and proper error handling
|
||||||
|
```
|
||||||
|
|
||||||
|
{% data variables.product.prodname_copilot_short %} might suggest:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
try {
|
||||||
|
await fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to fetch data', {
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE] This response is an example. {% data variables.copilot.copilot_chat_short %} responses are non-deterministic, so you may get a different response if you run the same prompt against the same code.
|
||||||
|
|
||||||
|
By adopting the in-the-moment fix approach, you help to ensure that substandard code does not get added to your codebase, and you avoid the creation of a backlog issue that may never be addressed.
|
||||||
|
|
||||||
|
For more details on using {% data variables.product.prodname_copilot_short %} in your IDE, see [AUTOTITLE](/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide).
|
||||||
|
|
||||||
|
## Using {% data variables.copilot.copilot_coding_agent %} for large-scale refactoring
|
||||||
|
|
||||||
|
Some refactoring tasks are just too big to complete while everyone on the team is busy developing new features. In this situation you can use {% data variables.copilot.copilot_coding_agent %} to handle these tasks autonomously. Human effort will still be required—at a minimum for reviewing the changes {% data variables.copilot.copilot_coding_agent %} proposes—but getting {% data variables.product.prodname_copilot_short %} to do the bulk of the work can allow you to carry out large-scale refactoring with much less impact on your team's productivity.
|
||||||
|
|
||||||
|
### When to use {% data variables.copilot.copilot_coding_agent %}
|
||||||
|
|
||||||
|
Use {% data variables.copilot.copilot_coding_agent %} for tasks that:
|
||||||
|
|
||||||
|
* Touch many files across your codebase
|
||||||
|
* Require systematic changes (like removing old feature flags)
|
||||||
|
* Need careful testing but are straightforward to implement
|
||||||
|
* Would interrupt feature development if done manually
|
||||||
|
|
||||||
|
Examples include:
|
||||||
|
|
||||||
|
* Framework upgrades that affect 50+ files
|
||||||
|
* Removing deprecated feature flags <!-- markdownlint-disable-line GHD046 -->
|
||||||
|
* Migrating to strict TypeScript
|
||||||
|
* Updating dependency versions
|
||||||
|
* Standardizing import patterns
|
||||||
|
|
||||||
|
### Workflow for {% data variables.copilot.copilot_coding_agent %}
|
||||||
|
|
||||||
|
1. Create a {% data variables.product.prodname_dotcom %} issue describing the refactoring task.
|
||||||
|
|
||||||
|
Be specific about what needs to change. For example:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Remove all feature flags marked for cleanup in Q2.
|
||||||
|
|
||||||
|
These flags are:
|
||||||
|
- `enable_new_dashboard`
|
||||||
|
- `beta_export_feature`
|
||||||
|
- `experimental_search`
|
||||||
|
|
||||||
|
All three flags are enabled by default in production.
|
||||||
|
|
||||||
|
Remove the flag checks and keep the "enabled" code path.
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Assign the issue to the **Copilot** user.
|
||||||
|
1. {% data variables.copilot.copilot_coding_agent %} will:
|
||||||
|
|
||||||
|
* Set up a development environment
|
||||||
|
* Open a draft pull request
|
||||||
|
* Make the required changes to the code
|
||||||
|
* Run your tests
|
||||||
|
* Finalize the pull request for review
|
||||||
|
* Request your review of the pull request
|
||||||
|
|
||||||
|
1. Review the pull request just as you would a pull request raised by a human.
|
||||||
|
1. Leave comments if changes are needed—{% data variables.copilot.copilot_coding_agent %} will update the pull request based on your feedback.
|
||||||
|
1. Iterate in this way until the work is completed correctly.
|
||||||
|
1. Approve and merge the pull request.
|
||||||
|
|
||||||
|
For more information, see [AUTOTITLE](/copilot/how-tos/use-copilot-agents/coding-agent/create-a-pr#assigning-an-issue-to-copilot) and [AUTOTITLE](/copilot/how-tos/use-copilot-agents/coding-agent/review-copilot-prs).
|
||||||
|
|
||||||
|
### Safety guardrails
|
||||||
|
|
||||||
|
{% data variables.copilot.copilot_coding_agent %} operates with built-in safety measures:
|
||||||
|
|
||||||
|
* It can only push to its own `copilot/*` branches
|
||||||
|
* It cannot merge pull requests—requires your approval
|
||||||
|
* All commits are logged and auditable
|
||||||
|
* Your existing branch protections remain active
|
||||||
|
* CI/CD checks run before any code is merged
|
||||||
|
|
||||||
|
## Creating custom instructions for your team
|
||||||
|
|
||||||
|
Custom instructions help {% data variables.product.prodname_copilot_short %} understand your team's coding standards and patterns. This ensures suggestions match your expectations from the start.
|
||||||
|
|
||||||
|
### Setting up custom instructions
|
||||||
|
|
||||||
|
1. In your repository, create a file named `.github/copilot-instructions.md`.
|
||||||
|
1. Add your team's coding standards in clear, straightforward statements—for example, using bulleted lists.
|
||||||
|
1. Commit the file to your repository.
|
||||||
|
|
||||||
|
### Example custom instructions
|
||||||
|
|
||||||
|
Here's an example of effective custom instructions:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
- Use structured logging, not console.log
|
||||||
|
- Sanitize user input before database queries
|
||||||
|
- Check for null/undefined on all optional parameters
|
||||||
|
- Keep functions under 50 lines (extract helpers if needed)
|
||||||
|
- Every public function needs a test
|
||||||
|
- Flag any loops that might trigger N+1 queries
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- Always use try-catch blocks for async operations
|
||||||
|
- Log errors with context (user ID, request ID, timestamp)
|
||||||
|
- Never swallow errors silently
|
||||||
|
- Return appropriate HTTP status codes
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
- Unit tests for all business logic
|
||||||
|
- Integration tests for API endpoints
|
||||||
|
- Mock external services in tests
|
||||||
|
- Test both success and failure paths
|
||||||
|
```
|
||||||
|
|
||||||
|
For detailed guidance on writing custom instructions, see [AUTOTITLE](/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot).
|
||||||
|
|
||||||
|
### Benefits of custom instructions
|
||||||
|
|
||||||
|
With custom instructions in place:
|
||||||
|
|
||||||
|
* {% data variables.product.prodname_copilot_short %} suggests code following your patterns
|
||||||
|
* Code reviews become faster, with fewer discussions about style changes
|
||||||
|
* New team members learn your standards through {% data variables.product.prodname_copilot_short %} suggestions
|
||||||
|
* Consistency improves across your codebase
|
||||||
|
|
||||||
|
## Running a pilot program
|
||||||
|
|
||||||
|
Start small to validate {% data variables.product.prodname_copilot_short %}'s impact on your technical debt before rolling it out widely.
|
||||||
|
|
||||||
|
### Week 1: Set up and establish baselines
|
||||||
|
|
||||||
|
1. Ensure all pilot participants have {% data variables.product.prodname_copilot_short %} access with {% data variables.copilot.copilot_coding_agent %} enabled.
|
||||||
|
1. Count the technical debt items in your backlog:
|
||||||
|
|
||||||
|
* Number of "tech debt", "chore", or similar labeled issues
|
||||||
|
* Number of outdated dependencies
|
||||||
|
* Number of files failing linter checks
|
||||||
|
|
||||||
|
1. Track current metrics:
|
||||||
|
|
||||||
|
* Average time from pull request creation to merge for refactoring PRs
|
||||||
|
* Average number of review rounds per refactoring PR
|
||||||
|
|
||||||
|
1. Create your first `.github/copilot-instructions.md` file with 3–5 of your most important standards.
|
||||||
|
|
||||||
|
### Weeks 2–4: Run the pilot
|
||||||
|
|
||||||
|
1. Select 5–10 repositories for your pilot.
|
||||||
|
1. Choose 1–2 specific problems to address. For example:
|
||||||
|
|
||||||
|
* Code duplication in a particular area
|
||||||
|
* Missing tests on frequently changed files
|
||||||
|
* Outdated dependencies
|
||||||
|
|
||||||
|
1. Use {% data variables.product.prodname_copilot_short %} in your IDE for quick fixes as you encounter issues.
|
||||||
|
1. Assign larger cleanup tasks to {% data variables.copilot.copilot_coding_agent %}.
|
||||||
|
1. Review all {% data variables.product.prodname_copilot_short %}-generated PRs carefully.
|
||||||
|
1. Provide feedback on suggestions to help {% data variables.product.prodname_copilot_short %} learn your preferences.
|
||||||
|
|
||||||
|
### Week 5: Evaluate results
|
||||||
|
|
||||||
|
After the pilot, measure your results:
|
||||||
|
|
||||||
|
* How much faster are refactoring pull requests getting merged?
|
||||||
|
* How many review rounds do they require now?
|
||||||
|
* Which types of code change suggestions, made by {% data variables.copilot.copilot_coding_agent %} in pull requests, did developers accept most often?
|
||||||
|
* Which suggestions needed the most revision?
|
||||||
|
* Are your technical debt metrics improving?
|
||||||
|
|
||||||
|
* Linter warnings decreasing?
|
||||||
|
* Test coverage increasing?
|
||||||
|
* Dependency versions more current?
|
||||||
|
|
||||||
|
Update your custom instructions based on what you learned about which guidance helped {% data variables.product.prodname_copilot_short %} most.
|
||||||
|
|
||||||
|
## Measuring success
|
||||||
|
|
||||||
|
Track specific metrics to understand {% data variables.product.prodname_copilot_short %}'s impact on your technical debt.
|
||||||
|
|
||||||
|
### Velocity metrics
|
||||||
|
|
||||||
|
Monitor how {% data variables.product.prodname_copilot_short %} affects development speed:
|
||||||
|
|
||||||
|
* Time to close technical debt issues (target: 30–50% reduction)
|
||||||
|
* Number of technical debt pull requests merged per week (target: 2–3x increase)
|
||||||
|
* Average number of review cycles per refactoring pull request (assess whether this increased or decreased)
|
||||||
|
|
||||||
|
### Quality metrics
|
||||||
|
|
||||||
|
Ensure quality improves alongside velocity:
|
||||||
|
|
||||||
|
* Linter warning count (this should trend downward)
|
||||||
|
* Test coverage percentage (this should trend upward)
|
||||||
|
* Number of production incidents related to refactored code (assess whether this changed)
|
||||||
|
|
||||||
|
### Engineer satisfaction
|
||||||
|
|
||||||
|
Survey your team regularly:
|
||||||
|
|
||||||
|
* Are engineers spending less time on routine maintenance?
|
||||||
|
* Are code reviews focusing more on architecture and less on style?
|
||||||
|
* Is onboarding faster for new team members?
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### {% data variables.product.prodname_copilot_short %} suggests incorrect changes
|
||||||
|
|
||||||
|
If {% data variables.product.prodname_copilot_short %} consistently suggests code that doesn't match your needs:
|
||||||
|
|
||||||
|
* Review your custom instructions—they may be too vague or contradictory
|
||||||
|
* Provide more specific context in your prompts
|
||||||
|
* Add examples of good code to your custom instructions
|
||||||
|
* Leave detailed feedback in pull request reviews to allow {% data variables.copilot.copilot_coding_agent %} to fix the problems
|
||||||
|
|
||||||
|
### Pull requests are too large to review
|
||||||
|
|
||||||
|
If {% data variables.copilot.copilot_coding_agent %} creates pull requests that are difficult to review:
|
||||||
|
|
||||||
|
* Break large tasks into smaller, focused issues
|
||||||
|
* Ask {% data variables.copilot.copilot_coding_agent %} to handle one file or directory at a time
|
||||||
|
* Use more specific issue descriptions
|
||||||
|
|
||||||
|
### Changes break tests
|
||||||
|
|
||||||
|
If refactoring introduces test failures:
|
||||||
|
|
||||||
|
* Ensure your test suite runs reliably before using {% data variables.copilot.copilot_coding_agent %}
|
||||||
|
* Review {% data variables.product.prodname_copilot_short %} changes carefully before merging
|
||||||
|
* Ask {% data variables.product.prodname_copilot_short %} to update tests along with the code changes
|
||||||
|
|
||||||
|
### Team adoption is slow
|
||||||
|
|
||||||
|
If your team isn't using {% data variables.product.prodname_copilot_short %} for technical debt:
|
||||||
|
|
||||||
|
* Share success stories from early adopters
|
||||||
|
* Demonstrate time savings in team meetings
|
||||||
|
* Start with the most annoying technical debt items
|
||||||
|
* Make creating custom instructions a team activity
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
In this tutorial, you learned how to use {% data variables.product.prodname_copilot_short %} to systematically reduce technical debt. You now know how to:
|
||||||
|
|
||||||
|
* Fix technical debt immediately using {% data variables.product.prodname_copilot_short %} in your IDE
|
||||||
|
* Assign large refactoring tasks to {% data variables.copilot.copilot_coding_agent %}
|
||||||
|
* Create custom instructions that align {% data variables.product.prodname_copilot_short %} with your team's standards
|
||||||
|
* Run a pilot program to validate the approach
|
||||||
|
* Measure {% data variables.product.prodname_copilot_short %}'s impact on technical debt
|
||||||
|
|
||||||
|
By automating routine refactoring and maintenance tasks, {% data variables.product.prodname_copilot_short %} frees you to focus on architecture, feature development, and other high-value work.
|
||||||
|
|
||||||
|
### Quick survey
|
||||||
|
|
||||||
|
{% note %}
|
||||||
|
|
||||||
|
After reading this tutorial, do you feel confident you can use {% data variables.product.prodname_copilot_short %} to reduce the technical debt in a codebase?
|
||||||
|
|
||||||
|
<a href="https://docs.github.io/success-test/yes.html" target="_blank" class="btn btn-outline mt-3 mr-3 no-underline"><span>Yes</span></a> <a href="https://docs.github.io/success-test/no.html" target="_blank" class="btn btn-outline mt-3 mr-3 no-underline"><span>No</span></a>
|
||||||
|
|
||||||
|
{% endnote %}
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
* **Expand your pilot**: Roll out to more repositories based on your pilot results.
|
||||||
|
* **Automate dependency updates**: Create recurring issues for {% data variables.copilot.copilot_coding_agent %} to handle dependency updates.
|
||||||
|
* **Build a refactoring queue**: Label issues in your backlog as good for {% data variables.product.prodname_copilot_short %} then regularly assign a batch of these to {% data variables.product.prodname_copilot_short %} to work on.
|
||||||
|
* **Share best practices**: Document successful prompts and custom instructions for your team.
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
* [AUTOTITLE](/copilot/using-github-copilot/coding-agent)
|
||||||
|
* [AUTOTITLE](/copilot/tutorials/refactoring-code-with-github-copilot)
|
||||||
|
* [How to use GitHub Copilot in your IDE: Tips, tricks, and best practices](https://github.blog/developer-skills/github/how-to-use-github-copilot-in-your-ide-tips-tricks-and-best-practices/) in the {% data variables.product.company_short %} blog
|
||||||
|
* [5 ways to integrate GitHub Copilot coding agent into your workflow](https://github.blog/ai-and-ml/github-copilot/5-ways-to-integrate-github-copilot-coding-agent-into-your-workflow/) in the {% data variables.product.company_short %} blog
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
| GHD058 | journey-tracks-liquid | Journey track properties must use valid Liquid syntax | error | frontmatter, journey-tracks, liquid |
|
| GHD058 | journey-tracks-liquid | Journey track properties must use valid Liquid syntax | error | frontmatter, journey-tracks, liquid |
|
||||||
| GHD059 | journey-tracks-guide-path-exists | Journey track guide paths must reference existing content files | error | frontmatter, journey-tracks |
|
| GHD059 | journey-tracks-guide-path-exists | Journey track guide paths must reference existing content files | error | frontmatter, journey-tracks |
|
||||||
| GHD060 | journey-tracks-unique-ids | Journey track IDs must be unique within a page | error | frontmatter, journey-tracks, unique-ids |
|
| GHD060 | journey-tracks-unique-ids | Journey track IDs must be unique within a page | error | frontmatter, journey-tracks, unique-ids |
|
||||||
| GHD061 | frontmatter-hero-image | Hero image paths must be absolute and point to valid images in /assets/images/banner-images/ | error | frontmatter, images |
|
| GHD061 | frontmatter-hero-image | Hero image paths must be absolute, extensionless, and point to valid images in /assets/images/banner-images/ | error | frontmatter, images |
|
||||||
| GHD062 | frontmatter-intro-links | introLinks keys must be valid keys defined in data/ui.yml under product_landing | error | frontmatter, single-source |
|
| GHD062 | frontmatter-intro-links | introLinks keys must be valid keys defined in data/ui.yml under product_landing | error | frontmatter, single-source |
|
||||||
| [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: octicon-<icon-name> | The octicon liquid syntax used is deprecated. Use this format instead `octicon "<octicon-name>" aria-label="<Octicon aria label>"` | error | |
|
| [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: octicon-<icon-name> | The octicon liquid syntax used is deprecated. Use this format instead `octicon "<octicon-name>" aria-label="<Octicon aria label>"` | error | |
|
||||||
| [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: site.data | Catch occurrences of deprecated liquid data syntax. | error | |
|
| [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: site.data | Catch occurrences of deprecated liquid data syntax. | error | |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface Frontmatter {
|
|||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the list of valid hero images
|
// Get the list of valid hero images (without extensions)
|
||||||
function getValidHeroImages(): string[] {
|
function getValidHeroImages(): string[] {
|
||||||
const ROOT = process.env.ROOT || '.'
|
const ROOT = process.env.ROOT || '.'
|
||||||
const heroImageDir = path.join(ROOT, 'assets/images/banner-images')
|
const heroImageDir = path.join(ROOT, 'assets/images/banner-images')
|
||||||
@@ -21,8 +21,11 @@ function getValidHeroImages(): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const files = fs.readdirSync(heroImageDir)
|
const files = fs.readdirSync(heroImageDir)
|
||||||
// Return absolute paths as they would appear in frontmatter
|
// Return absolute paths without extensions as they should appear in frontmatter
|
||||||
return files.map((file) => `/assets/images/banner-images/${file}`)
|
return files.map((file) => {
|
||||||
|
const baseName = path.basename(file, path.extname(file))
|
||||||
|
return `/assets/images/banner-images/${baseName}`
|
||||||
|
})
|
||||||
} catch {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -31,7 +34,7 @@ function getValidHeroImages(): string[] {
|
|||||||
export const frontmatterHeroImage: Rule = {
|
export const frontmatterHeroImage: Rule = {
|
||||||
names: ['GHD061', 'frontmatter-hero-image'],
|
names: ['GHD061', 'frontmatter-hero-image'],
|
||||||
description:
|
description:
|
||||||
'Hero image paths must be absolute and point to valid images in /assets/images/banner-images/',
|
'Hero image paths must be absolute, extensionless, and point to valid images in /assets/images/banner-images/',
|
||||||
tags: ['frontmatter', 'images'],
|
tags: ['frontmatter', 'images'],
|
||||||
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
// Only check index.md files
|
// Only check index.md files
|
||||||
@@ -70,7 +73,22 @@ export const frontmatterHeroImage: Rule = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the file actually exists
|
// Check if the path includes a file extension (which is not allowed)
|
||||||
|
if (path.extname(heroImage)) {
|
||||||
|
const line = params.lines.find((ln: string) => ln.trim().startsWith('heroImage:'))
|
||||||
|
const lineNumber = line ? params.lines.indexOf(line) + 1 : 1
|
||||||
|
const withoutExtension = heroImage.substring(0, heroImage.lastIndexOf('.'))
|
||||||
|
addError(
|
||||||
|
onError,
|
||||||
|
lineNumber,
|
||||||
|
`Hero image path must not include file extension. Use: ${withoutExtension}`,
|
||||||
|
line || '',
|
||||||
|
null, // No fix possible
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file with this base name actually exists
|
||||||
const validHeroImages = getValidHeroImages()
|
const validHeroImages = getValidHeroImages()
|
||||||
if (validHeroImages.length > 0 && !validHeroImages.includes(heroImage)) {
|
if (validHeroImages.length > 0 && !validHeroImages.includes(heroImage)) {
|
||||||
const line = params.lines.find((ln: string) => ln.trim().startsWith('heroImage:'))
|
const line = params.lines.find((ln: string) => ln.trim().startsWith('heroImage:'))
|
||||||
|
|||||||
@@ -6,7 +6,24 @@ import { frontmatterHeroImage } from '../../lib/linting-rules/frontmatter-hero-i
|
|||||||
const fmOptions = { markdownlintOptions: { frontMatter: null } }
|
const fmOptions = { markdownlintOptions: { frontMatter: null } }
|
||||||
|
|
||||||
describe(frontmatterHeroImage.names.join(' - '), () => {
|
describe(frontmatterHeroImage.names.join(' - '), () => {
|
||||||
test('valid absolute heroImage path passes', async () => {
|
test('valid absolute extensionless heroImage path passes', async () => {
|
||||||
|
const markdown = [
|
||||||
|
'---',
|
||||||
|
'title: Test',
|
||||||
|
"heroImage: '/assets/images/banner-images/hero-1'",
|
||||||
|
'---',
|
||||||
|
'',
|
||||||
|
'# Test',
|
||||||
|
].join('\n')
|
||||||
|
const result = await runRule(frontmatterHeroImage, {
|
||||||
|
strings: { 'content/test/index.md': markdown },
|
||||||
|
...fmOptions,
|
||||||
|
})
|
||||||
|
const errors = result['content/test/index.md']
|
||||||
|
expect(errors.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('heroImage path with extension fails', async () => {
|
||||||
const markdown = [
|
const markdown = [
|
||||||
'---',
|
'---',
|
||||||
'title: Test',
|
'title: Test',
|
||||||
@@ -20,7 +37,8 @@ describe(frontmatterHeroImage.names.join(' - '), () => {
|
|||||||
...fmOptions,
|
...fmOptions,
|
||||||
})
|
})
|
||||||
const errors = result['content/test/index.md']
|
const errors = result['content/test/index.md']
|
||||||
expect(errors.length).toBe(0)
|
expect(errors.length).toBe(1)
|
||||||
|
expect(errors[0].errorDetail).toContain('must not include file extension')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('non-index.md file is ignored', async () => {
|
test('non-index.md file is ignored', async () => {
|
||||||
@@ -51,14 +69,9 @@ describe(frontmatterHeroImage.names.join(' - '), () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('relative heroImage path fails', async () => {
|
test('relative heroImage path fails', async () => {
|
||||||
const markdown = [
|
const markdown = ['---', 'title: Test', "heroImage: 'images/hero-1'", '---', '', '# Test'].join(
|
||||||
'---',
|
'\n',
|
||||||
'title: Test',
|
)
|
||||||
"heroImage: 'images/hero-1.png'",
|
|
||||||
'---',
|
|
||||||
'',
|
|
||||||
'# Test',
|
|
||||||
].join('\n')
|
|
||||||
const result = await runRule(frontmatterHeroImage, {
|
const result = await runRule(frontmatterHeroImage, {
|
||||||
strings: { 'content/test/index.md': markdown },
|
strings: { 'content/test/index.md': markdown },
|
||||||
...fmOptions,
|
...fmOptions,
|
||||||
@@ -72,7 +85,7 @@ describe(frontmatterHeroImage.names.join(' - '), () => {
|
|||||||
const markdown = [
|
const markdown = [
|
||||||
'---',
|
'---',
|
||||||
'title: Test',
|
'title: Test',
|
||||||
"heroImage: '/assets/images/other/hero-1.png'",
|
"heroImage: '/assets/images/other/hero-1'",
|
||||||
'---',
|
'---',
|
||||||
'',
|
'',
|
||||||
'# Test',
|
'# Test',
|
||||||
@@ -90,7 +103,7 @@ describe(frontmatterHeroImage.names.join(' - '), () => {
|
|||||||
const markdown = [
|
const markdown = [
|
||||||
'---',
|
'---',
|
||||||
'title: Test',
|
'title: Test',
|
||||||
"heroImage: '/assets/images/banner-images/non-existent.png'",
|
"heroImage: '/assets/images/banner-images/non-existent'",
|
||||||
'---',
|
'---',
|
||||||
'',
|
'',
|
||||||
'# Test',
|
'# Test',
|
||||||
@@ -105,14 +118,14 @@ describe(frontmatterHeroImage.names.join(' - '), () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('all valid hero images pass', async () => {
|
test('all valid hero images pass', async () => {
|
||||||
// Test each valid hero image
|
// Test each valid hero image (extensionless)
|
||||||
const validImages = [
|
const validImages = [
|
||||||
"heroImage: '/assets/images/banner-images/hero-1.png'",
|
"heroImage: '/assets/images/banner-images/hero-1'",
|
||||||
"heroImage: '/assets/images/banner-images/hero-2.png'",
|
"heroImage: '/assets/images/banner-images/hero-2'",
|
||||||
"heroImage: '/assets/images/banner-images/hero-3.png'",
|
"heroImage: '/assets/images/banner-images/hero-3'",
|
||||||
"heroImage: '/assets/images/banner-images/hero-4.png'",
|
"heroImage: '/assets/images/banner-images/hero-4'",
|
||||||
"heroImage: '/assets/images/banner-images/hero-5.png'",
|
"heroImage: '/assets/images/banner-images/hero-5'",
|
||||||
"heroImage: '/assets/images/banner-images/hero-6.png'",
|
"heroImage: '/assets/images/banner-images/hero-6'",
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const heroImageLine of validImages) {
|
for (const heroImageLine of validImages) {
|
||||||
|
|||||||
Reference in New Issue
Block a user