diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..0792b6fd6f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,92 @@
+# Dependabot configuration for automated dependency updates
+# This helps keep dependencies secure and up-to-date
+version: 2
+updates:
+ # Enable version updates for Go modules
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "09:00"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "go"
+ commit-message:
+ prefix: "chore(deps)"
+ groups:
+ go-dependencies:
+ patterns:
+ - "*"
+ update-types:
+ - "minor"
+ - "patch"
+
+ # Enable version updates for npm (web directory)
+ - package-ecosystem: "npm"
+ directory: "/web"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "09:00"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "javascript"
+ commit-message:
+ prefix: "chore(deps)"
+ groups:
+ npm-dependencies:
+ patterns:
+ - "*"
+ update-types:
+ - "minor"
+ - "patch"
+
+ # Enable version updates for npm (docs directory)
+ - package-ecosystem: "npm"
+ directory: "/docs"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "09:00"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "javascript"
+ - "documentation"
+ commit-message:
+ prefix: "chore(deps)"
+
+ # Enable version updates for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "09:00"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "github-actions"
+ commit-message:
+ prefix: "chore(deps)"
+ groups:
+ github-actions:
+ patterns:
+ - "*"
+
+ # Enable version updates for Docker
+ - package-ecosystem: "docker"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "09:00"
+ open-pull-requests-limit: 3
+ labels:
+ - "dependencies"
+ - "docker"
+ commit-message:
+ prefix: "chore(deps)"
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
new file mode 100644
index 0000000000..240752f771
--- /dev/null
+++ b/.github/release-drafter.yml
@@ -0,0 +1,81 @@
+name-template: 'v$RESOLVED_VERSION'
+tag-template: 'v$RESOLVED_VERSION'
+
+categories:
+ - title: '๐ Features'
+ labels:
+ - 'kind/feature'
+ - 'enhancement'
+ - 'kind/enhancement'
+ - title: '๐ Bug Fixes'
+ labels:
+ - 'kind/bug'
+ - 'bug'
+ - 'fix'
+ - title: '๐ Documentation'
+ labels:
+ - 'documentation'
+ - 'docs'
+ - title: '๐ Security'
+ labels:
+ - 'security'
+ - title: 'โก Performance'
+ labels:
+ - 'performance'
+ - title: '๐งน Maintenance'
+ labels:
+ - 'chore'
+ - 'dependencies'
+ - 'refactor'
+
+change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
+change-title-escapes: '\<*_&'
+
+version-resolver:
+ major:
+ labels:
+ - 'major'
+ - 'breaking'
+ minor:
+ labels:
+ - 'minor'
+ - 'kind/feature'
+ patch:
+ labels:
+ - 'patch'
+ - 'kind/bug'
+ default: patch
+
+exclude-labels:
+ - 'skip-changelog'
+ - 'not-auto-close'
+
+autolabeler:
+ - label: 'documentation'
+ files:
+ - '*.md'
+ - 'docs/**/*'
+ - label: 'kind/bug'
+ branch:
+ - '/fix\/.+/'
+ title:
+ - '/fix/i'
+ - label: 'kind/feature'
+ branch:
+ - '/feat(ure)?\/.+/'
+ title:
+ - '/feat(ure)?/i'
+ - label: 'dependencies'
+ files:
+ - 'go.mod'
+ - 'go.sum'
+ - 'package.json'
+ - 'package-lock.json'
+ - 'yarn.lock'
+
+template: |
+ ## What's Changed
+
+ $CHANGES
+
+ **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
diff --git a/.github/workflows/auto-assign-reviewers.yaml b/.github/workflows/auto-assign-reviewers.yaml
new file mode 100644
index 0000000000..09cd54e9df
--- /dev/null
+++ b/.github/workflows/auto-assign-reviewers.yaml
@@ -0,0 +1,89 @@
+name: auto-assign-reviewers
+
+on:
+ pull_request:
+ types: [opened, ready_for_review]
+ branches:
+ - master
+ - 'release-v*'
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ assign-reviewers:
+ runs-on: ubuntu-24.04
+ if: github.event.pull_request.draft == false
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Auto-assign reviewers based on changed files
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const prNumber = context.payload.pull_request.number;
+
+ // Get changed files
+ const { data: files } = await github.rest.pulls.listFiles({
+ owner,
+ repo,
+ pull_number: prNumber
+ });
+
+ const reviewers = new Set();
+ const teamReviewers = new Set();
+
+ // Analyze changed files and assign appropriate reviewers
+ const hasGoChanges = files.some(f => f.filename.endsWith('.go'));
+ const hasWebChanges = files.some(f => f.filename.startsWith('web/'));
+ const hasWorkflowChanges = files.some(f => f.filename.startsWith('.github/workflows/'));
+ const hasDocChanges = files.some(f => f.filename.endsWith('.md'));
+ const hasManifestChanges = files.some(f => f.filename.startsWith('manifests/'));
+
+ // Add team reviewers based on changes
+ if (hasWebChanges) {
+ teamReviewers.add('pipecd-approvers-web');
+ }
+
+ // Always add general approvers for significant changes
+ if (hasGoChanges || hasWorkflowChanges || hasManifestChanges) {
+ teamReviewers.add('pipecd-approvers');
+ }
+
+ // Assign reviewers if any were identified
+ if (teamReviewers.size > 0) {
+ try {
+ await github.rest.pulls.requestReviewers({
+ owner,
+ repo,
+ pull_number: prNumber,
+ team_reviewers: Array.from(teamReviewers)
+ });
+
+ console.log(`Assigned team reviewers: ${Array.from(teamReviewers).join(', ')}`);
+ } catch (error) {
+ console.log('Could not assign team reviewers:', error.message);
+ }
+ }
+
+ // Add helpful labels
+ const labels = [];
+ if (hasGoChanges) labels.push('area/go');
+ if (hasWebChanges) labels.push('area/web');
+ if (hasWorkflowChanges) labels.push('area/build');
+ if (hasDocChanges) labels.push('kind/documentation');
+ if (hasManifestChanges) labels.push('area/manifests');
+
+ if (labels.length > 0) {
+ await github.rest.issues.addLabels({
+ owner,
+ repo,
+ issue_number: prNumber,
+ labels
+ });
+
+ console.log(`Added labels: ${labels.join(', ')}`);
+ }
diff --git a/.github/workflows/auto-merge-dependabot.yaml b/.github/workflows/auto-merge-dependabot.yaml
new file mode 100644
index 0000000000..eb7cc0fff0
--- /dev/null
+++ b/.github/workflows/auto-merge-dependabot.yaml
@@ -0,0 +1,64 @@
+name: auto-merge-dependabot
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ auto-merge-dependabot:
+ runs-on: ubuntu-24.04
+ if: github.actor == 'dependabot[bot]'
+ steps:
+ - name: Fetch Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v2
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
+ - name: Auto-approve minor and patch updates
+ if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
+ run: |
+ gh pr review --approve "${{ github.event.pull_request.html_url }}"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Enable auto-merge for safe updates
+ if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
+ run: |
+ gh pr merge --auto --squash "${{ github.event.pull_request.html_url }}"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Comment on major updates
+ if: steps.metadata.outputs.update-type == 'version-update:semver-major'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const prNumber = context.payload.pull_request.number;
+
+ const comment = `## โ ๏ธ Major Version Update Detected
+
+This Dependabot PR contains a **major version update** for:
+- **${context.payload.pull_request.title}**
+
+**Action required:**
+- Manual review needed for breaking changes
+- Check changelog and migration guides
+- Update code if necessary
+- Run full test suite before merging
+
+**Update type:** \`${process.env.UPDATE_TYPE}\``;
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: prNumber,
+ body: comment
+ });
+ env:
+ UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }}
diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml
new file mode 100644
index 0000000000..c8fc1df164
--- /dev/null
+++ b/.github/workflows/benchmark.yaml
@@ -0,0 +1,77 @@
+name: benchmark
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.go'
+ - 'go.mod'
+ - 'go.sum'
+ - '.github/workflows/benchmark.yaml'
+
+# Only run the latest benchmark for each PR
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ GO_VERSION: 1.25.0
+
+jobs:
+ benchmark:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: ${{ env.GO_VERSION }}
+ cache: true
+
+ - name: Run benchmarks
+ run: |
+ # Run benchmarks and save results
+ go test -bench=. -benchmem -run=^$ ./... | tee benchmark_results.txt
+
+ - name: Upload benchmark results
+ uses: actions/upload-artifact@v4
+ with:
+ name: benchmark-results
+ path: benchmark_results.txt
+ retention-days: 30
+
+ - name: Compare benchmarks (PR only)
+ if: github.event_name == 'pull_request'
+ uses: benchmark-action/github-action-benchmark@v1
+ with:
+ tool: 'go'
+ output-file-path: benchmark_results.txt
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ comment-on-alert: true
+ alert-threshold: '150%'
+ fail-on-alert: false
+ auto-push: false
+
+ - name: Comment benchmark results
+ if: github.event_name == 'pull_request'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const results = fs.readFileSync('benchmark_results.txt', 'utf8');
+
+ // Extract summary (first 1000 chars)
+ const summary = results.substring(0, 1000);
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: `## ๐ Benchmark Results\n\n\`\`\`\n${summary}\n...\n\`\`\`\n\nFull results are available in the workflow artifacts.`
+ });
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index dee768c3ba..6e2af601ab 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -12,6 +12,11 @@ on:
- 'release-v*'
- 'feat/*'
+# Cancel in-progress runs for the same workflow and ref
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
env:
GO_VERSION: 1.25.0
NODE_VERSION: 18.12.0
diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml
new file mode 100644
index 0000000000..490bbf7c4f
--- /dev/null
+++ b/.github/workflows/changelog.yaml
@@ -0,0 +1,43 @@
+name: changelog
+
+on:
+ push:
+ branches:
+ - master
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: read
+
+jobs:
+ update-changelog:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+
+ - name: Generate changelog
+ id: changelog
+ uses: orhun/git-cliff-action@v4
+ with:
+ config: cliff.toml
+ args: --verbose
+ env:
+ OUTPUT: CHANGELOG.md
+
+ - name: Commit changelog
+ run: |
+ git config user.name 'github-actions[bot]'
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
+
+ # Only commit if there are changes
+ if [[ $(git diff --stat) != '' ]]; then
+ git add CHANGELOG.md
+ git commit -m "docs: update CHANGELOG.md"
+ git push
+ else
+ echo "No changes to CHANGELOG.md"
+ fi
diff --git a/.github/workflows/ci-failure-analyzer.yaml b/.github/workflows/ci-failure-analyzer.yaml
new file mode 100644
index 0000000000..1b67b13fe6
--- /dev/null
+++ b/.github/workflows/ci-failure-analyzer.yaml
@@ -0,0 +1,164 @@
+name: ci-failure-analyzer
+
+on:
+ workflow_run:
+ workflows: ["build", "test", "lint"]
+ types: [completed]
+
+permissions:
+ actions: read
+ checks: read
+ pull-requests: write
+
+jobs:
+ analyze-failure:
+ runs-on: ubuntu-24.04
+ if: github.event.workflow_run.conclusion == 'failure'
+ steps:
+ - name: Analyze CI failure and comment
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const workflowRun = context.payload.workflow_run;
+
+ // Get associated PRs
+ const prs = workflowRun.pull_requests;
+ if (prs.length === 0) {
+ console.log('No PRs associated with this workflow run');
+ return;
+ }
+
+ const prNumber = prs[0].number;
+
+ // Get workflow jobs
+ const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
+ owner,
+ repo,
+ run_id: workflowRun.id
+ });
+
+ const failedJobs = jobs.jobs.filter(job => job.conclusion === 'failure');
+
+ if (failedJobs.length === 0) {
+ return;
+ }
+
+ // Analyze failure patterns
+ let failureAnalysis = `## ๐ด CI Failure Analysis\n\n`;
+ failureAnalysis += `Workflow: **${workflowRun.name}**\n`;
+ failureAnalysis += `Failed jobs: **${failedJobs.length}**\n\n`;
+
+ const failurePatterns = {
+ test: [],
+ lint: [],
+ build: [],
+ timeout: [],
+ dependency: [],
+ other: []
+ };
+
+ for (const job of failedJobs) {
+ const jobName = job.name.toLowerCase();
+ const steps = job.steps.filter(s => s.conclusion === 'failure');
+
+ failureAnalysis += `### โ ${job.name}\n`;
+
+ for (const step of steps) {
+ const stepName = step.name.toLowerCase();
+
+ // Categorize failures
+ if (stepName.includes('test')) {
+ failurePatterns.test.push({ job: job.name, step: step.name });
+ } else if (stepName.includes('lint') || stepName.includes('format')) {
+ failurePatterns.lint.push({ job: job.name, step: step.name });
+ } else if (stepName.includes('build') || stepName.includes('compile')) {
+ failurePatterns.build.push({ job: job.name, step: step.name });
+ } else if (stepName.includes('timeout')) {
+ failurePatterns.timeout.push({ job: job.name, step: step.name });
+ } else if (stepName.includes('dependencies') || stepName.includes('install')) {
+ failurePatterns.dependency.push({ job: job.name, step: step.name });
+ } else {
+ failurePatterns.other.push({ job: job.name, step: step.name });
+ }
+
+ failureAnalysis += `- Step: \`${step.name}\`\n`;
+ }
+
+ failureAnalysis += `- [View logs](${job.html_url})\n\n`;
+ }
+
+ // Add helpful suggestions
+ failureAnalysis += `## ๐ก Suggested Actions\n\n`;
+
+ if (failurePatterns.test.length > 0) {
+ failureAnalysis += `**Test Failures (${failurePatterns.test.length}):**\n`;
+ failureAnalysis += `- Review test output in the logs\n`;
+ failureAnalysis += `- Run tests locally: \`make test\`\n`;
+ failureAnalysis += `- Check for flaky tests\n\n`;
+ }
+
+ if (failurePatterns.lint.length > 0) {
+ failureAnalysis += `**Linting Issues (${failurePatterns.lint.length}):**\n`;
+ failureAnalysis += `- Run linter locally: \`make lint\`\n`;
+ failureAnalysis += `- Auto-fix where possible: \`make fmt\`\n`;
+ failureAnalysis += `- Review [coding standards](https://github.com/pipe-cd/pipecd/blob/master/CONTRIBUTING.md)\n\n`;
+ }
+
+ if (failurePatterns.build.length > 0) {
+ failureAnalysis += `**Build Failures (${failurePatterns.build.length}):**\n`;
+ failureAnalysis += `- Build locally: \`make build\`\n`;
+ failureAnalysis += `- Check for compilation errors\n`;
+ failureAnalysis += `- Verify all dependencies are available\n\n`;
+ }
+
+ if (failurePatterns.dependency.length > 0) {
+ failureAnalysis += `**Dependency Issues (${failurePatterns.dependency.length}):**\n`;
+ failureAnalysis += `- Update dependencies: \`make update/go-deps\` or \`make update/web-deps\`\n`;
+ failureAnalysis += `- Clear caches and retry\n\n`;
+ }
+
+ if (failurePatterns.timeout.length > 0) {
+ failureAnalysis += `**Timeout Issues (${failurePatterns.timeout.length}):**\n`;
+ failureAnalysis += `- Check for infinite loops or hanging processes\n`;
+ failureAnalysis += `- Review test performance\n\n`;
+ }
+
+ failureAnalysis += `---\n`;
+ failureAnalysis += `**Quick actions:**\n`;
+ failureAnalysis += `- ๐ Re-run failed workflows\n`;
+ failureAnalysis += `- ๐ Check [CI troubleshooting guide](https://github.com/pipe-cd/pipecd/blob/master/CONTRIBUTING.md#development)\n`;
+ failureAnalysis += `- ๐ฌ Ask in [Slack](https://cloud-native.slack.com/archives/C01B27F9T0X) if you need help\n`;
+
+ // Check if we already commented
+ const { data: comments } = await github.rest.issues.listComments({
+ owner,
+ repo,
+ issue_number: prNumber
+ });
+
+ const existingComment = comments.find(c =>
+ c.user.type === 'Bot' &&
+ c.body.includes('CI Failure Analysis') &&
+ c.body.includes(workflowRun.id.toString())
+ );
+
+ if (existingComment) {
+ // Update existing comment
+ await github.rest.issues.updateComment({
+ owner,
+ repo,
+ comment_id: existingComment.id,
+ body: failureAnalysis + `\n\nWorkflow run: ${workflowRun.id}`
+ });
+ } else {
+ // Create new comment
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: prNumber,
+ body: failureAnalysis + `\n\nWorkflow run: ${workflowRun.id}`
+ });
+ }
+
+ console.log(`Posted failure analysis for PR #${prNumber}`);
diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml
new file mode 100644
index 0000000000..36a8ef703d
--- /dev/null
+++ b/.github/workflows/dependency-review.yaml
@@ -0,0 +1,28 @@
+name: dependency-review
+
+on:
+ pull_request:
+ branches:
+ - master
+ - 'release-v*'
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ dependency-review:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Dependency Review
+ uses: actions/dependency-review-action@v4
+ with:
+ # Fail the build if there are any vulnerabilities
+ fail-on-severity: moderate
+ # Comment on the PR with the dependency review results
+ comment-summary-in-pr: always
+ # Allow licenses
+ allow-licenses: Apache-2.0, MIT, BSD-2-Clause, BSD-3-Clause, ISC
diff --git a/.github/workflows/issue-triage-automation.yaml b/.github/workflows/issue-triage-automation.yaml
new file mode 100644
index 0000000000..abd98e077e
--- /dev/null
+++ b/.github/workflows/issue-triage-automation.yaml
@@ -0,0 +1,142 @@
+name: issue-triage-automation
+
+on:
+ issues:
+ types: [opened, labeled]
+ issue_comment:
+ types: [created]
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ triage-new-issue:
+ runs-on: ubuntu-24.04
+ if: github.event_name == 'issues' && github.event.action == 'opened'
+ steps:
+ - name: Auto-label and triage new issues
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const issue = context.payload.issue;
+ const issueNumber = issue.number;
+ const title = issue.title.toLowerCase();
+ const body = issue.body?.toLowerCase() || '';
+
+ const labels = [];
+
+ // Auto-detect issue type from title/body
+ if (title.includes('bug') || body.includes('bug')) {
+ labels.push('kind/bug');
+ }
+ if (title.includes('feature') || title.includes('enhancement') || body.includes('feature request')) {
+ labels.push('kind/feature');
+ }
+ if (title.includes('question') || title.includes('help') || title.includes('how to')) {
+ labels.push('kind/question');
+ }
+ if (title.includes('doc') || title.includes('documentation')) {
+ labels.push('kind/documentation');
+ }
+
+ // Auto-detect area from keywords
+ if (body.includes('kubernetes') || body.includes('k8s')) {
+ labels.push('area/kubernetes');
+ }
+ if (body.includes('terraform')) {
+ labels.push('area/terraform');
+ }
+ if (body.includes('web') || body.includes('ui') || body.includes('frontend')) {
+ labels.push('area/web');
+ }
+ if (body.includes('pipeline') || body.includes('deployment')) {
+ labels.push('area/deployment');
+ }
+ if (body.includes('security') || body.includes('vulnerability') || body.includes('cve')) {
+ labels.push('security');
+ }
+
+ // Add triage label
+ labels.push('needs-triage');
+
+ // Add labels
+ if (labels.length > 0) {
+ await github.rest.issues.addLabels({
+ owner,
+ repo,
+ issue_number: issueNumber,
+ labels
+ });
+
+ console.log(`Auto-labeled issue #${issueNumber} with: ${labels.join(', ')}`);
+ }
+
+ // Welcome message for first-time contributors
+ const { data: issueCreator } = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ creator: issue.user.login,
+ state: 'all'
+ });
+
+ if (issueCreator.length === 1) {
+ const welcomeComment = `๐ Welcome to PipeCD, @${issue.user.login}!
+
+Thank you for opening your first issue. A maintainer will review this soon.
+
+**While you wait:**
+- Check our [documentation](https://pipecd.dev/docs/)
+- Join our [Slack community](https://cloud-native.slack.com/archives/C01B27F9T0X)
+- Review our [contributing guide](https://github.com/pipe-cd/pipecd/blob/master/CONTRIBUTING.md)
+
+We appreciate your contribution! ๐`;
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: issueNumber,
+ body: welcomeComment
+ });
+ }
+
+ check-inactive-issues:
+ runs-on: ubuntu-24.04
+ if: github.event_name == 'issue_comment'
+ steps:
+ - name: Remove needs-info label when author responds
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const issue = context.payload.issue;
+ const comment = context.payload.comment;
+
+ // If issue author responds, remove needs-info label
+ if (comment.user.login === issue.user.login) {
+ const { data: labels } = await github.rest.issues.listLabelsOnIssue({
+ owner,
+ repo,
+ issue_number: issue.number
+ });
+
+ if (labels.some(l => l.name === 'needs-info')) {
+ await github.rest.issues.removeLabel({
+ owner,
+ repo,
+ issue_number: issue.number,
+ name: 'needs-info'
+ }).catch(() => {});
+
+ // Add needs-triage back
+ await github.rest.issues.addLabels({
+ owner,
+ repo,
+ issue_number: issue.number,
+ labels: ['needs-triage']
+ });
+
+ console.log(`Removed needs-info from issue #${issue.number} after author response`);
+ }
+ }
diff --git a/.github/workflows/link-check.yaml b/.github/workflows/link-check.yaml
new file mode 100644
index 0000000000..92c33c56b9
--- /dev/null
+++ b/.github/workflows/link-check.yaml
@@ -0,0 +1,28 @@
+name: link-check
+
+on:
+ pull_request:
+ branches:
+ - master
+ - 'release-v*'
+ paths:
+ - '**.md'
+ - 'docs/**'
+ schedule:
+ # Run weekly on Monday at 00:00 UTC
+ - cron: '0 0 * * 1'
+ workflow_dispatch:
+
+jobs:
+ markdown-link-check:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Check links in markdown files
+ uses: gaurav-nelson/github-action-markdown-link-check@v1
+ with:
+ config-file: '.markdown-link-check.json'
+ use-quiet-mode: 'yes'
+ use-verbose-mode: 'no'
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index db70d646f4..6f676301e3 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -10,6 +10,11 @@ on:
- "release-v*"
- "feat/*"
+# Cancel in-progress runs for the same workflow and ref
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
env:
GO_VERSION: 1.25.0
NODE_VERSION: 18.12.0
diff --git a/.github/workflows/pr-size-labeler.yaml b/.github/workflows/pr-size-labeler.yaml
new file mode 100644
index 0000000000..f4ad853ff5
--- /dev/null
+++ b/.github/workflows/pr-size-labeler.yaml
@@ -0,0 +1,130 @@
+name: pr-size-labeler
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ label-pr-size:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+
+ - name: Label PR by size and complexity
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const prNumber = context.payload.pull_request.number;
+
+ // Get PR files
+ const { data: files } = await github.rest.pulls.listFiles({
+ owner,
+ repo,
+ pull_number: prNumber,
+ per_page: 100
+ });
+
+ // Calculate total changes
+ let additions = 0;
+ let deletions = 0;
+ let filesChanged = files.length;
+
+ files.forEach(file => {
+ additions += file.additions;
+ deletions += file.deletions;
+ });
+
+ const totalChanges = additions + deletions;
+
+ // Remove old size labels
+ const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
+ owner,
+ repo,
+ issue_number: prNumber
+ });
+
+ const sizeLabels = ['size/XS', 'size/S', 'size/M', 'size/L', 'size/XL', 'size/XXL'];
+ const labelsToRemove = currentLabels
+ .filter(label => sizeLabels.includes(label.name))
+ .map(label => label.name);
+
+ for (const label of labelsToRemove) {
+ await github.rest.issues.removeLabel({
+ owner,
+ repo,
+ issue_number: prNumber,
+ name: label
+ }).catch(() => {});
+ }
+
+ // Determine size label
+ let sizeLabel;
+ if (totalChanges < 10) {
+ sizeLabel = 'size/XS';
+ } else if (totalChanges < 50) {
+ sizeLabel = 'size/S';
+ } else if (totalChanges < 200) {
+ sizeLabel = 'size/M';
+ } else if (totalChanges < 500) {
+ sizeLabel = 'size/L';
+ } else if (totalChanges < 1000) {
+ sizeLabel = 'size/XL';
+ } else {
+ sizeLabel = 'size/XXL';
+ }
+
+ // Add new size label
+ await github.rest.issues.addLabels({
+ owner,
+ repo,
+ issue_number: prNumber,
+ labels: [sizeLabel]
+ });
+
+ // Add comment for large PRs
+ if (totalChanges > 500) {
+ const comment = `## โ ๏ธ Large PR Detected
+
+This PR has **${totalChanges}** total changes across **${filesChanged}** files.
+
+**Recommendations for large PRs:**
+- Consider breaking this into smaller, focused PRs
+- Ensure comprehensive testing
+- Add detailed description of changes
+- Request review from multiple maintainers
+
+**Current changes:**
+- โ ${additions} additions
+- โ ${deletions} deletions
+- ๐ ${filesChanged} files changed`;
+
+ // Check if we already commented
+ const { data: comments } = await github.rest.issues.listComments({
+ owner,
+ repo,
+ issue_number: prNumber
+ });
+
+ const hasComment = comments.some(c =>
+ c.user.type === 'Bot' && c.body.includes('Large PR Detected')
+ );
+
+ if (!hasComment) {
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: prNumber,
+ body: comment
+ });
+ }
+ }
+
+ console.log(`Labeled PR #${prNumber} as ${sizeLabel} (${totalChanges} changes)`);
diff --git a/.github/workflows/release-notes.yaml b/.github/workflows/release-notes.yaml
new file mode 100644
index 0000000000..95571038d8
--- /dev/null
+++ b/.github/workflows/release-notes.yaml
@@ -0,0 +1,65 @@
+name: release-notes
+
+on:
+ release:
+ types: [published]
+
+permissions:
+ contents: write
+
+jobs:
+ generate-release-notes:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+
+ - name: Generate Release Notes
+ id: generate_notes
+ uses: release-drafter/release-drafter@v6
+ with:
+ config-name: release-drafter.yml
+ publish: true
+ tag: ${{ github.event.release.tag_name }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Update Release
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { data: release } = await github.rest.repos.getRelease({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ release_id: context.payload.release.id
+ });
+
+ const body = `${{ steps.generate_notes.outputs.body }}
+
+ ## Installation
+
+ ### Helm Chart
+ \`\`\`bash
+ helm repo add pipecd https://charts.pipecd.dev
+ helm repo update
+ helm install pipecd pipecd/pipecd --version ${{ github.event.release.tag_name }}
+ \`\`\`
+
+ ### Binary Downloads
+ - [Linux AMD64](https://github.com/pipe-cd/pipecd/releases/download/${{ github.event.release.tag_name }}/pipecd_linux_amd64.tar.gz)
+ - [Linux ARM64](https://github.com/pipe-cd/pipecd/releases/download/${{ github.event.release.tag_name }}/pipecd_linux_arm64.tar.gz)
+ - [macOS AMD64](https://github.com/pipe-cd/pipecd/releases/download/${{ github.event.release.tag_name }}/pipecd_darwin_amd64.tar.gz)
+ - [macOS ARM64](https://github.com/pipe-cd/pipecd/releases/download/${{ github.event.release.tag_name }}/pipecd_darwin_arm64.tar.gz)
+
+ ## What's Changed
+ ${release.body || ''}
+ `;
+
+ await github.rest.repos.updateRelease({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ release_id: context.payload.release.id,
+ body: body
+ });
diff --git a/.github/workflows/security-scan.yaml b/.github/workflows/security-scan.yaml
new file mode 100644
index 0000000000..2966678dc4
--- /dev/null
+++ b/.github/workflows/security-scan.yaml
@@ -0,0 +1,144 @@
+name: security-scan
+
+on:
+ pull_request:
+ branches:
+ - master
+ - 'release-v*'
+ push:
+ branches:
+ - master
+ schedule:
+ # Run weekly on Monday at 00:00 UTC
+ - cron: '0 0 * * 1'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ security-events: write
+ pull-requests: write
+
+jobs:
+ trivy-scan:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Run Trivy vulnerability scanner in repo mode
+ uses: aquasecurity/trivy-action@0.28.0
+ with:
+ scan-type: 'fs'
+ scan-ref: '.'
+ format: 'sarif'
+ output: 'trivy-results.sarif'
+ severity: 'CRITICAL,HIGH'
+
+ - name: Upload Trivy results to GitHub Security
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: 'trivy-results.sarif'
+
+ docker-scan:
+ runs-on: ubuntu-24.04
+ strategy:
+ matrix:
+ dockerfile:
+ - path: 'cmd/pipecd/Dockerfile'
+ context: '.'
+ - path: 'cmd/piped/Dockerfile'
+ context: '.'
+ - path: 'tool/piped-base/Dockerfile'
+ context: 'tool/piped-base'
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Build Docker image
+ run: |
+ docker build -f ${{ matrix.dockerfile.path }} -t test-image:latest ${{ matrix.dockerfile.context }}
+
+ - name: Run Trivy vulnerability scanner on Docker image
+ uses: aquasecurity/trivy-action@0.28.0
+ with:
+ image-ref: 'test-image:latest'
+ format: 'sarif'
+ output: 'trivy-docker-results.sarif'
+ severity: 'CRITICAL,HIGH'
+
+ - name: Upload Docker scan results
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: 'trivy-docker-results.sarif'
+ category: ${{ matrix.dockerfile.path }}
+
+ gosec-scan:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Run Gosec Security Scanner
+ uses: securego/gosec@v2.21.4
+ with:
+ args: '-fmt sarif -out gosec-results.sarif ./...'
+
+ - name: Upload Gosec results
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: 'gosec-results.sarif'
+
+ npm-audit:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+
+ - name: Run npm audit
+ working-directory: ./web
+ run: |
+ npm audit --audit-level=high --json > npm-audit.json || true
+
+ - name: Upload npm audit results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: npm-audit-results
+ path: web/npm-audit.json
+ retention-days: 30
+
+ security-summary:
+ runs-on: ubuntu-24.04
+ needs: [trivy-scan, gosec-scan, npm-audit]
+ if: always() && github.event_name == 'pull_request'
+ steps:
+ - name: Post security scan summary
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const prNumber = context.payload.pull_request.number;
+
+ const summary = `## ๐ Security Scan Results
+
+ Security scans have been completed for this PR:
+ - โ
Trivy vulnerability scan (filesystem)
+ - โ
Gosec security scan (Go code)
+ - โ
npm audit (web dependencies)
+ - โ
Docker image scanning
+
+ Check the [Security tab](https://github.com/${owner}/${repo}/security) for detailed results.
+
+ Critical and high severity issues are automatically flagged.`;
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: prNumber,
+ body: summary
+ });
diff --git a/.github/workflows/spell-check.yaml b/.github/workflows/spell-check.yaml
new file mode 100644
index 0000000000..9014b84710
--- /dev/null
+++ b/.github/workflows/spell-check.yaml
@@ -0,0 +1,23 @@
+name: spell-check
+
+on:
+ pull_request:
+ branches:
+ - master
+ - 'release-v*'
+ paths:
+ - '**.md'
+ - 'docs/**'
+ - '.github/workflows/spell-check.yaml'
+
+jobs:
+ spell-check:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Check spelling
+ uses: crate-ci/typos@v1.24.6
+ with:
+ config: .typos.toml
diff --git a/.github/workflows/stale-pr-reminder.yaml b/.github/workflows/stale-pr-reminder.yaml
new file mode 100644
index 0000000000..6448b2c0c2
--- /dev/null
+++ b/.github/workflows/stale-pr-reminder.yaml
@@ -0,0 +1,132 @@
+name: stale-pr-reminder
+
+on:
+ schedule:
+ # Run daily at 00:00 UTC
+ - cron: '0 0 * * *'
+ workflow_dispatch:
+
+permissions:
+ pull-requests: write
+ issues: write
+
+jobs:
+ stale-pr-reminder:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Check and comment on stale PRs
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const msPerDay = 86400000;
+ const staleDays = 7;
+ const reviewRequestDays = 3;
+
+ // Get all open PRs
+ const { data: prs } = await github.rest.pulls.list({
+ owner,
+ repo,
+ state: 'open',
+ sort: 'updated',
+ direction: 'asc',
+ per_page: 100
+ });
+
+ const now = new Date();
+
+ for (const pr of prs) {
+ const updatedAt = new Date(pr.updated_at);
+ const daysSinceUpdate = (now - updatedAt) / msPerDay;
+
+ // Skip draft PRs
+ if (pr.draft) continue;
+
+ // Get reviews
+ const { data: reviews } = await github.rest.pulls.listReviews({
+ owner,
+ repo,
+ pull_number: pr.number
+ });
+
+ // Get review requests
+ const { data: reviewRequests } = await github.rest.pulls.listRequestedReviewers({
+ owner,
+ repo,
+ pull_number: pr.number
+ });
+
+ const hasReviewRequests = reviewRequests.users.length > 0 || reviewRequests.teams.length > 0;
+ const hasApproval = reviews.some(r => r.state === 'APPROVED');
+ const hasChangesRequested = reviews.some(r => r.state === 'CHANGES_REQUESTED');
+
+ // Ping reviewers if no response after 3 days
+ if (hasReviewRequests && daysSinceUpdate >= reviewRequestDays) {
+ const reviewers = [
+ ...reviewRequests.users.map(u => `@${u.login}`),
+ ...reviewRequests.teams.map(t => `@pipe-cd/${t.slug}`)
+ ].join(', ');
+
+ const comment = `## ๐ Review Reminder
+
+${reviewers} - This PR has been waiting for review for ${Math.floor(daysSinceUpdate)} days.
+
+**PR:** #${pr.number} - ${pr.title}
+**Author:** @${pr.user.login}
+
+Please take a moment to review when you have time. If you're no longer the right reviewer, please re-assign.`;
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: pr.number,
+ body: comment
+ });
+
+ console.log(`Pinged reviewers for PR #${pr.number}`);
+ }
+
+ // Ping author if changes were requested and no update
+ if (hasChangesRequested && !hasApproval && daysSinceUpdate >= staleDays) {
+ const comment = `## ๐ Friendly Reminder
+
+@${pr.user.login} - This PR has requested changes that haven't been addressed in ${Math.floor(daysSinceUpdate)} days.
+
+**Next steps:**
+- Address the review comments
+- Push updates to trigger new reviews
+- Respond to reviewers if you need clarification
+
+Let us know if you need any help!`;
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: pr.number,
+ body: comment
+ });
+
+ console.log(`Reminded author for PR #${pr.number}`);
+ }
+
+ // Remind to merge if approved but not merged
+ if (hasApproval && !hasChangesRequested && daysSinceUpdate >= staleDays) {
+ const comment = `## โ
Ready to Merge?
+
+This PR has been approved for ${Math.floor(daysSinceUpdate)} days.
+
+**Status:** Approved and ready for merge
+**Author:** @${pr.user.login}
+
+If all CI checks pass, this can be merged! ๐`;
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: pr.number,
+ body: comment
+ });
+
+ console.log(`Reminded to merge PR #${pr.number}`);
+ }
+ }
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index b91cf1612d..a8df8d4bf7 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -12,6 +12,11 @@ on:
- 'release-v*'
- 'feat/*'
+# Cancel in-progress runs for the same workflow and ref
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
env:
GO_VERSION: 1.25.0
NODE_VERSION: 18.12.0
diff --git a/.github/workflows/update-actions.yaml b/.github/workflows/update-actions.yaml
new file mode 100644
index 0000000000..d7e9812364
--- /dev/null
+++ b/.github/workflows/update-actions.yaml
@@ -0,0 +1,52 @@
+name: update-actions
+
+on:
+ schedule:
+ # Run monthly on the first day at 00:00 UTC
+ - cron: '0 0 1 * *'
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ update-actions:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Update GitHub Actions
+ id: update
+ run: |
+ # This is a placeholder for automated action updates
+ # In production, you would use a tool like Renovate or Dependabot
+ echo "Actions are managed by Dependabot"
+ echo "updated=false" >> $GITHUB_OUTPUT
+
+ - name: Create Pull Request
+ if: steps.update.outputs.updated == 'true'
+ uses: peter-evans/create-pull-request@v6
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ commit-message: 'chore(deps): update GitHub Actions'
+ title: 'chore(deps): Update GitHub Actions to latest versions'
+ body: |
+ ## Description
+
+ This PR updates GitHub Actions to their latest versions.
+
+ ## Changes
+ - Updated actions to latest stable versions
+ - Maintained compatibility with existing workflows
+
+ ## Testing
+ - [ ] All workflows pass with updated actions
+ - [ ] No breaking changes detected
+ branch: update-actions
+ delete-branch: true
+ labels: |
+ dependencies
+ github-actions
+ automated
diff --git a/.markdown-link-check.json b/.markdown-link-check.json
new file mode 100644
index 0000000000..6ab9883750
--- /dev/null
+++ b/.markdown-link-check.json
@@ -0,0 +1,27 @@
+{
+ "ignorePatterns": [
+ {
+ "pattern": "^http://localhost"
+ },
+ {
+ "pattern": "^http://127.0.0.1"
+ },
+ {
+ "pattern": "^https://pipecd.dev/docs/"
+ }
+ ],
+ "replacementPatterns": [],
+ "httpHeaders": [
+ {
+ "urls": ["https://github.com"],
+ "headers": {
+ "Accept": "application/vnd.github.v3+json"
+ }
+ }
+ ],
+ "timeout": "20s",
+ "retryOn429": true,
+ "retryCount": 3,
+ "fallbackRetryDelay": "30s",
+ "aliveStatusCodes": [200, 206, 299, 403]
+}
diff --git a/.typos.toml b/.typos.toml
new file mode 100644
index 0000000000..d3764633d4
--- /dev/null
+++ b/.typos.toml
@@ -0,0 +1,35 @@
+# Configuration for typos spell checker
+# See https://github.com/crate-ci/typos
+
+[default]
+extend-ignore-re = [
+ # Ignore git SHAs
+ "[0-9a-f]{7,40}",
+ # Ignore base64 encoded strings
+ "[A-Za-z0-9+/]{20,}={0,2}",
+]
+
+[files]
+extend-exclude = [
+ "*.sum",
+ "*.lock",
+ "package-lock.json",
+ "yarn.lock",
+ ".artifacts/",
+ "vendor/",
+ "node_modules/",
+ "web/build/",
+ "docs/public/",
+]
+
+[default.extend-words]
+# Add project-specific words that are not typos
+pipecd = "pipecd"
+piped = "piped"
+pipectl = "pipectl"
+kubectl = "kubectl"
+kubernetes = "kubernetes"
+kubeconfig = "kubeconfig"
+gRPC = "gRPC"
+GitOps = "GitOps"
+CNCF = "CNCF"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index eead6e4abd..da6a61311f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -153,7 +153,7 @@ By contributing to PipeCD, you agree that your contributions will be licensed un
### Release Note and Breaking Changes
-If your change introcudes a user-facing change, please update the following section in your PR description.
+If your change introduces a user-facing change, please update the following section in your PR description.
```md
**Does this PR introduce a user-facing change?**:
diff --git a/cliff.toml b/cliff.toml
new file mode 100644
index 0000000000..2bfaa9cffd
--- /dev/null
+++ b/cliff.toml
@@ -0,0 +1,56 @@
+# Configuration for git-cliff
+# See https://git-cliff.org/docs/configuration
+
+[changelog]
+header = """
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+"""
+body = """
+{% if version %}\
+ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
+{% else %}\
+ ## [Unreleased]
+{% endif %}\
+{% for group, commits in commits | group_by(attribute="group") %}
+ ### {{ group | upper_first }}
+ {% for commit in commits %}
+ - {% if commit.breaking %}[**BREAKING**] {% endif %}{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))\
+ {% endfor %}
+{% endfor %}\n
+"""
+footer = """
+
+"""
+trim = true
+
+[git]
+conventional_commits = true
+filter_unconventional = true
+split_commits = false
+commit_preprocessors = []
+commit_parsers = [
+ { message = "^feat", group = "Features" },
+ { message = "^fix", group = "Bug Fixes" },
+ { message = "^doc", group = "Documentation" },
+ { message = "^perf", group = "Performance" },
+ { message = "^refactor", group = "Refactoring" },
+ { message = "^style", group = "Styling" },
+ { message = "^test", group = "Testing" },
+ { message = "^chore\\(release\\): prepare for", skip = true },
+ { message = "^chore\\(deps\\)", group = "Dependencies" },
+ { message = "^chore", group = "Miscellaneous Tasks" },
+ { body = ".*security", group = "Security" },
+]
+protect_breaking_commits = false
+filter_commits = false
+tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+"
+skip_tags = "v0.1.0-beta.1"
+ignore_tags = ""
+topo_order = false
+sort_commits = "oldest"
diff --git a/docs/content/en/docs-dev/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-dev/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-dev/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-dev/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.44.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.44.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index a18c9d22cd..c9201453d8 100644
--- a/docs/content/en/docs-v0.44.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.44.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.45.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.45.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index a18c9d22cd..c9201453d8 100644
--- a/docs/content/en/docs-v0.45.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.45.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.46.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.46.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.46.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.46.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.47.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.47.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.47.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.47.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.48.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.48.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.48.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.48.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.49.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.49.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.49.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.49.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.50.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.50.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.50.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.50.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.51.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.51.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.51.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.51.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.52.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.52.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.52.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.52.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.53.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.53.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.53.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.53.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.54.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.54.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.54.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.54.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can:
diff --git a/docs/content/en/docs-v0.55.x/user-guide/managing-application/customizing-deployment/custom-sync.md b/docs/content/en/docs-v0.55.x/user-guide/managing-application/customizing-deployment/custom-sync.md
index 47d7d7a534..c077b3753b 100644
--- a/docs/content/en/docs-v0.55.x/user-guide/managing-application/customizing-deployment/custom-sync.md
+++ b/docs/content/en/docs-v0.55.x/user-guide/managing-application/customizing-deployment/custom-sync.md
@@ -43,7 +43,7 @@ spec:
Note:
1. You can use `CUSTOM_SYNC` with any current supporting application kind, but keep `alwaysUsePipeline` true to not run the application kind's default `QUICK_SYNC`.
2. Only one `CUSTOM_SYNC` stage should be used in an application pipeline.
-3. The commands run with the enviroment variable `PATH` that refers `~/.piped/tools` at first.
+3. The commands run with the environment variable `PATH` that refers `~/.piped/tools` at first.
The public piped image available in PipeCD main repo (ref: [Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/cmd/piped/Dockerfile)) is based on [alpine](https://hub.docker.com/_/alpine/) and only has a few UNIX command available (ref: [piped-base Dockerfile](https://github.com/pipe-cd/pipecd/blob/master/tool/piped-base/Dockerfile)). If you want to use your commands (`sam` in the above example), you can: