From a3dc699737b617febc659d47eea047cc37bbbf4c Mon Sep 17 00:00:00 2001 From: Martin Pokorny <89339813+mPokornyETM@users.noreply.github.com> Date: Thu, 25 Dec 2025 11:33:53 +0100 Subject: [PATCH 01/10] Update README with current extensions list Added a list of currently included extensions in the README. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index de7b899..c8a39f2 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ A small Visual Studio Code extension pack that groups useful extensions for [SIMATIC WinCC Open Architecture](https://www.siemens.com/global/en/products/automation/industry-software/automation-software/scada/simatic-wincc-oa.html). +Currently added extensions: + ++ mPokornyETM.wincc-oa-projects ++ RichardJanisch.winccoa-script-actions ++ RichardJanisch.winccoa-vscode-logviewer ++ RichardJanisch.winccoa-vscode-tests ++ RichardJanisch.winccoa-sidepanel ++ RichardJanisch.winccoa-ctrl + + --- ## Install From 1ab0bb5370d68fc014622ebd7b0bd0f3bf6f1b95 Mon Sep 17 00:00:00 2001 From: Martin Pokorny <89339813+mPokornyETM@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:41:04 +0100 Subject: [PATCH 02/10] chore(release): Release v0.0.6 (#13) Provide automated release management --- .github/DISCUSSIONS.md | 72 ++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 142 +++++++++++ .github/ISSUE_TEMPLATE/documentation.yml | 85 +++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 108 ++++++++ .github/ISSUE_TEMPLATE/question.yml | 85 +++++++ .github/PULL_REQUEST_TEMPLATE.md | 27 -- .../PULL_REQUEST_TEMPLATE/feature-bugfix.md | 33 +++ .github/PULL_REQUEST_TEMPLATE/release.md | 28 +++ .github/dependabot.yml | 74 ++++++ .github/labeler.yml | 19 ++ .github/repository.settings.yml | 32 +++ .github/rulesets/01-develop.yml | 40 +++ .github/rulesets/02-main.yml | 40 +++ .github/rulesets/03-release-hotfix.yml | 18 ++ .github/rulesets/04-tags.yml | 17 ++ .github/scripts/generate_settings_payloads.py | 92 +++++++ .../workflows/apply-settings-and-rulesets.yml | 185 ++++++++++++++ .github/workflows/build-winccoa-image.yml | 92 +++++++ .github/workflows/ci-cd.yml | 208 ++++++++++++++++ .github/workflows/ci.yml | 34 --- .github/workflows/create-release-branch.yml | 186 ++++++++++++++ .github/workflows/gitflow-validation.yml | 167 +++++++++++++ .github/workflows/gitflow.yml | 34 +++ .github/workflows/pr-labels.yml | 23 ++ .github/workflows/pre-release-develop.yml | 57 +++++ .github/workflows/pre-release.yml | 232 ++++-------------- .github/workflows/prerelease-reusable.yml | 113 +++++++++ .github/workflows/release-reusable.yml | 164 +++++++++++++ .github/workflows/release.yml | 216 ++-------------- .github/workflows/required-checks.yml | 31 +++ .github/workflows/setup-gitflow.yml | 153 ++++++++++++ .github/workflows/sync-org-files.yml | 38 +++ .github/workflows/sync-template-files.yml | 38 +++ FUNDING.yml | 3 + LICENSE | 2 +- SECURITY.md | 30 +++ package-lock.json | 4 +- package.json | 2 +- 38 files changed, 2486 insertions(+), 438 deletions(-) create mode 100644 .github/DISCUSSIONS.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/feature-bugfix.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/release.md create mode 100644 .github/dependabot.yml create mode 100644 .github/labeler.yml create mode 100644 .github/repository.settings.yml create mode 100644 .github/rulesets/01-develop.yml create mode 100644 .github/rulesets/02-main.yml create mode 100644 .github/rulesets/03-release-hotfix.yml create mode 100644 .github/rulesets/04-tags.yml create mode 100644 .github/scripts/generate_settings_payloads.py create mode 100644 .github/workflows/apply-settings-and-rulesets.yml create mode 100644 .github/workflows/build-winccoa-image.yml create mode 100644 .github/workflows/ci-cd.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/create-release-branch.yml create mode 100644 .github/workflows/gitflow-validation.yml create mode 100644 .github/workflows/gitflow.yml create mode 100644 .github/workflows/pr-labels.yml create mode 100644 .github/workflows/pre-release-develop.yml create mode 100644 .github/workflows/prerelease-reusable.yml create mode 100644 .github/workflows/release-reusable.yml create mode 100644 .github/workflows/required-checks.yml create mode 100644 .github/workflows/setup-gitflow.yml create mode 100644 .github/workflows/sync-org-files.yml create mode 100644 .github/workflows/sync-template-files.yml create mode 100644 FUNDING.yml create mode 100644 SECURITY.md diff --git a/.github/DISCUSSIONS.md b/.github/DISCUSSIONS.md new file mode 100644 index 0000000..ee85f43 --- /dev/null +++ b/.github/DISCUSSIONS.md @@ -0,0 +1,72 @@ +# GitHub Discussions Guide + +Welcome to the community discussions! + +## Discussion Categories + +**πŸ“’ Announcements** +Official project updates, releases, and important notices. + +**πŸ’‘ Ideas** +Feature requests, suggestions, and enhancement proposals. + +**πŸ™‹β€β™€οΈ Q&A** +Technical support, troubleshooting, and how-to questions. + +**πŸ› οΈ Show and Tell** +Share your projects, configurations, and success stories. + +**πŸ”§ Development** +Development discussions, architecture decisions, and contribution coordination. + +**🌐 General** +Community chat and general WinCC OA related discussions. + +## Posting Guidelines + +### Before You Post + +1. Search existing discussions to avoid duplicates +2. Choose the appropriate category +3. Write a clear, descriptive title +4. Include relevant context and environment details + +### For Questions + +Include your environment details: + +- Package Version: (e.g., 1.0.0) +- Node.js Version: (e.g., 18.x) +- npm Version: (e.g., 9.x) +- Operating System: (e.g., Windows 11) + +Describe the issue clearly with steps to reproduce. + +### For Feature Ideas + +- Describe the problem you're trying to solve +- Explain your proposed solution +- Include use cases and examples +- Consider alternatives you've thought about + +## Community Guidelines + +**Be Respectful** +Treat everyone with kindness and respect. + +**Stay On-Topic** +Keep discussions related to the project and library usage. + +**Provide Value** +Share knowledge, help others, and give constructive feedback. + +**Use Best Practices** +Format code properly, use clear titles, and include context. + +## Quick Links + +- [Documentation](README.md) +- [Bug Reports](../../issues) +- [Contributing Guide](CONTRIBUTING.md) + +Let's build an amazing community! diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..cf2e656 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,142 @@ +name: πŸ› Bug Report +description: Report a problem with the WinCC OA VS Code Extension +title: "[Bug]: " +labels: ["bug", "needs-triage"] +projects: [] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug! πŸ› + + Please provide as much detail as possible to help us reproduce and fix the issue. + + - type: dropdown + id: severity + attributes: + label: Severity + description: How severe is this bug? + options: + - Critical (Extension completely unusable) + - High (Major functionality broken) + - Medium (Feature partially broken) + - Low (Minor issue or cosmetic) + validations: + required: true + + - type: textarea + id: description + attributes: + label: Bug Description + description: A clear and concise description of what the bug is + placeholder: Describe what happened and what you expected to happen + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: What you expected to happen + placeholder: Describe what should have happened instead + + - type: textarea + id: actual-behavior + attributes: + label: Actual Behavior + description: What actually happened + placeholder: Describe what actually happened + + - type: textarea + id: error-logs + attributes: + label: Error Messages/Logs + description: Copy any error messages or relevant log output + placeholder: Paste error messages, stack traces, or log output here + render: text + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem + placeholder: You can paste screenshots directly here + + - type: input + id: extension-version + attributes: + label: Extension Version + description: What version of the WinCC OA extension are you using? + placeholder: "e.g., 1.0.0" + validations: + required: true + + - type: input + id: vscode-version + attributes: + label: VS Code Version + description: What version of VS Code are you using? + placeholder: "e.g., 1.94.0" + validations: + required: true + + - type: dropdown + id: winccoa-version + attributes: + label: WinCC OA Version + description: Which WinCC OA version are you using? + options: + - "3.21" + - "3.20" + - "3.19" + - "3.18" + - "Multiple versions" + - "Not applicable" + validations: + required: true + + - type: dropdown + id: operating-system + attributes: + label: Operating System + description: What operating system are you using? + options: + - Windows 11 + - Windows 10 + - Windows Server 2022 + - Windows Server 2019 + - Linux (specify in additional context) + - Other + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Add any other context about the problem here + placeholder: Any additional information that might help us understand the issue + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..cffd9d0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,85 @@ +name: πŸ“– Documentation Issue +description: Report an issue with documentation or suggest improvements +title: "[Docs]: " +labels: ["documentation", "needs-triage"] +projects: [] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for helping improve our documentation! πŸ“š + + Please describe the documentation issue or improvement you'd like to suggest. + + - type: dropdown + id: doc-type + attributes: + label: Documentation Type + description: What type of documentation is this about? + options: + - README + - API Documentation + - User Guide + - Developer Guide + - Code Comments + - Configuration Examples + - Troubleshooting + - Other + validations: + required: true + + - type: dropdown + id: issue-type + attributes: + label: Issue Type + description: What kind of documentation issue is this? + options: + - Missing documentation + - Incorrect information + - Outdated information + - Unclear explanation + - Typo/Grammar + - Broken link + - Missing example + - Improvement suggestion + validations: + required: true + + - type: input + id: location + attributes: + label: Documentation Location + description: Where is this documentation located? + placeholder: "e.g., README.md, docs/api.md, line 42 of extension.ts" + + - type: textarea + id: current-content + attributes: + label: Current Content (if applicable) + description: Quote the current content that needs to be changed + placeholder: Copy the existing text here if applicable + + - type: textarea + id: suggested-content + attributes: + label: Suggested Content + description: What should the documentation say instead? + placeholder: Provide your suggested improvement or new content + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any additional context about this documentation issue + placeholder: Why is this change needed? Who would benefit from it? + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..e895e41 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,108 @@ +name: πŸš€ Feature Request +description: Suggest a new feature for the WinCC OA VS Code Extension +title: "[Feature]: " +labels: ["enhancement", "needs-triage"] +projects: [] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to suggest a new feature! πŸŽ‰ + + Please provide as much detail as possible to help us understand your request. + + - type: dropdown + id: priority + attributes: + label: Priority + description: How important is this feature to you? + options: + - Critical (Blocks current work) + - High (Important for productivity) + - Medium (Nice to have) + - Low (Future consideration) + validations: + required: true + + - type: dropdown + id: component + attributes: + label: Component + description: Which part of the extension does this feature relate to? + options: + - Project Management (lifecycle, registration) + - Code Editor (syntax, completion) + - Testing & Quality (test execution, analysis) + - Documentation (generation, templates) + - Integration (API, CI/CD) + - User Interface (tree view, dialogs) + - Configuration (settings, preferences) + - Other + validations: + required: true + + - type: textarea + id: user-story + attributes: + label: User Story + description: Describe the feature from a user's perspective + placeholder: "As a WinCC OA developer, I want [feature] so that [benefit]" + validations: + required: true + + - type: textarea + id: current-behavior + attributes: + label: Current Behavior + description: Describe how the extension currently works in this area + placeholder: Describe the current state or limitations + + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: Describe your ideal solution + placeholder: Describe what you would like to see implemented + + - type: textarea + id: acceptance-criteria + attributes: + label: Acceptance Criteria + description: What conditions must be met for this feature to be considered complete? + placeholder: | + - [ ] Criterion 1 + - [ ] Criterion 2 + - [ ] Criterion 3 + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Add any other context, screenshots, or examples + placeholder: Any additional information that might help us understand your request + + - type: dropdown + id: winccoa-version + attributes: + label: WinCC OA Version + description: Which WinCC OA version are you using? + options: + - "3.21" + - "3.20" + - "3.19" + - "3.18" + - "Multiple versions" + - "Not applicable" + validations: + required: true + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..c9203b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,85 @@ +name: ❓ Question / Support +description: Ask a question or get support for using the extension +title: "[Question]: " +labels: ["question", "needs-triage"] +projects: [] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Have a question about using the WinCC OA VS Code Extension? πŸ€” + + Please check our [documentation](../README.md) first, then ask your question below. + + - type: dropdown + id: question-type + attributes: + label: Question Type + description: What type of question is this? + options: + - How to use a feature + - Configuration help + - Troubleshooting + - Best practices + - Integration guidance + - Performance optimization + - Other + validations: + required: true + + - type: textarea + id: question + attributes: + label: Your Question + description: What would you like to know? + placeholder: Please describe your question in detail + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: Provide context about what you're trying to accomplish + placeholder: | + - What are you trying to achieve? + - What have you already tried? + - What specific part are you stuck on? + + - type: input + id: extension-version + attributes: + label: Extension Version + description: What version of the WinCC OA extension are you using? + placeholder: "e.g., 1.0.0" + + - type: dropdown + id: winccoa-version + attributes: + label: WinCC OA Version + description: Which WinCC OA version are you using? + options: + - "3.21" + - "3.20" + - "3.19" + - "3.18" + - "Multiple versions" + - "Not applicable" + + - type: textarea + id: additional-info + attributes: + label: Additional Information + description: Any additional information that might be helpful + placeholder: Screenshots, configuration files, error messages, etc. + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 52753a8..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,27 +0,0 @@ - - -## Summary - - - -## Related Issue - -- Fixes # (issue number) or relates to # - -## Type of change - -- [ ] Bug fix -- [ ] New feature -- [ ] Documentation update -- [ ] Other (please describe) - -## Checklist - -- [ ] I have read the contributing guidelines -- [ ] My code follows the repository style -- [ ] I added tests for my changes (if applicable) -- [ ] All checks pass - -## Additional notes - - diff --git a/.github/PULL_REQUEST_TEMPLATE/feature-bugfix.md b/.github/PULL_REQUEST_TEMPLATE/feature-bugfix.md new file mode 100644 index 0000000..a486cd5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/feature-bugfix.md @@ -0,0 +1,33 @@ +# Feature / Bugfix Pull Request + +## πŸ“‹ Description + + + +> Note: Git Flow and Conventional Commits are validated automatically by the **Git Flow Validation** GitHub Action. + +## πŸ”— Related Issue(s) + + + +- Fixes # +- Closes # +- Related to # + +## πŸ§ͺ Testing + + + +- [ ] Manual testing + +## πŸš€ Pre-release (Beta) + + + +- [ ] I verified the Beta pre-release artifact (if generated) + +## πŸ“‹ Additional Notes + + + +/cc @mPokornyETM diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md new file mode 100644 index 0000000..9b56d81 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -0,0 +1,28 @@ +# Release / Hotfix Pull Request + +## 🌳 Branch Type + +- [ ] Release (`release/*` β†’ `main`) +- [ ] Hotfix (`hotfix/*` β†’ `main`) + +## πŸ“‹ Description + + + +> Note: Git Flow and Conventional Commits are validated automatically by the **Git Flow Validation** GitHub Action. + +## πŸš€ Pre-release (Alpha) + + + +- [ ] I verified the Alpha pre-release artifact (if generated) + +## πŸ§ͺ Testing / Validation + +- [ ] Smoke test completed +- [ ] Regression risk assessed +- [ ] Rollback plan (hotfix) + +## πŸ“‹ Additional Notes + +/cc @mPokornyETM diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bb71f97 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,74 @@ +# Dependabot configuration for automated dependency updates +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + # Enable version updates for npm (Node.js dependencies) + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Europe/Prague" + open-pull-requests-limit: 10 + commit-message: + prefix: "deps" + prefix-development: "deps-dev" + include: "scope" + labels: + - "dependencies" + - "size/small" + # Group related updates together + groups: + # Group all TypeScript related dependencies + typescript: + patterns: + - "typescript" + - "@types/*" + - "ts-*" + update-types: + - "minor" + - "patch" + + + # Group testing dependencies + testing: + patterns: + - "*test*" + - "*jest*" + - "*mocha*" + - "*chai*" + - "@types/node" + update-types: + - "minor" + - "patch" + + # Group development tools + dev-tools: + patterns: + - "eslint*" + - "prettier*" + - "@typescript-eslint/*" + - "webpack*" + - "esbuild*" + update-types: + - "minor" + - "patch" + + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:30" + timezone: "Europe/Prague" + open-pull-requests-limit: 5 + commit-message: + prefix: "ci" + include: "scope" + labels: + - "dependencies" + - "area/build" + - "size/small" \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..21baa5d --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,19 @@ + +# .github/labeler.yml β€” actions/labeler v6 schema + +frontend: + - changed-files: + - any-glob-to-any-file: 'src/**' + +ci: + - changed-files: + - any-glob-to-any-file: '.github/workflows/**' + +docs: + - changed-files: + - any-glob-to-any-file: 'README.md' + - any-glob-to-any-file: 'docs/**' + +tests: + - changed-files: + - any-glob-to-any-file: 'test/**' diff --git a/.github/repository.settings.yml b/.github/repository.settings.yml new file mode 100644 index 0000000..d7b3a12 --- /dev/null +++ b/.github/repository.settings.yml @@ -0,0 +1,32 @@ +# Repository settings applied via GitHub REST API by .github/workflows/apply-settings-and-rulesets.yml +# This repo uses GitFlow: develop is default, main is stable release. + +description: "VS Code Tools Pack for WinCC OA" +homepage: "https://github.com/winccoa-tools-pack" +default_branch: "develop" + +has_issues: true +has_wiki: false + +allow_squash_merge: true +allow_merge_commit: false +allow_rebase_merge: false + +delete_branch_on_merge: true + +topics: + - winccoa + - wincc-oa + - vscode + - automation + - vscode-extension + - vscode-extension-pack + - scada + - hmi + - industrial-automation + - Siemens + - ETM + +security: + enable_vulnerability_alerts: true + enable_automated_security_fixes: true diff --git a/.github/rulesets/01-develop.yml b/.github/rulesets/01-develop.yml new file mode 100644 index 0000000..462c91a --- /dev/null +++ b/.github/rulesets/01-develop.yml @@ -0,0 +1,40 @@ +name: "Develop branch protection" +target: "branch" +# Start in evaluate mode to avoid blocking merges during rollout. +enforcement: "active" + +conditions: + ref_name: + include: + - "refs/heads/develop" + exclude: [] + +rules: + - type: "non_fast_forward" + parameters: {} + + - type: "deletion" + parameters: {} + + - type: "pull_request" + parameters: + required_approving_review_count: 1 + dismiss_stale_reviews_on_push: true + require_code_owner_review: false + require_last_push_approval: false + required_review_thread_resolution: false + + - type: "required_status_checks" + parameters: + strict_required_status_checks_policy: true + required_status_checks: + - context: "Required Checks / Gate / wait" + + - type: "required_linear_history" + parameters: {} + +# Allow organization admins to bypass the PR review requirement (but keep other protections). +bypass_actors: + - actor_id: 1 + actor_type: "OrganizationAdmin" + bypass_mode: "pull_request" diff --git a/.github/rulesets/02-main.yml b/.github/rulesets/02-main.yml new file mode 100644 index 0000000..9c30b51 --- /dev/null +++ b/.github/rulesets/02-main.yml @@ -0,0 +1,40 @@ +name: "Main (stable) branch protection" +target: "branch" +# Start in evaluate mode to avoid blocking merges during rollout. +enforcement: "active" + +conditions: + ref_name: + include: + - "refs/heads/main" + exclude: [] + +rules: + - type: "non_fast_forward" + parameters: {} + + - type: "deletion" + parameters: {} + + - type: "pull_request" + parameters: + required_approving_review_count: 1 + dismiss_stale_reviews_on_push: true + require_code_owner_review: false + require_last_push_approval: false + required_review_thread_resolution: true + + - type: "required_status_checks" + parameters: + strict_required_status_checks_policy: true + required_status_checks: + - context: "Required Checks / Gate / wait" + + - type: "required_linear_history" + parameters: {} + +# Allow organization admins to bypass the PR review requirement (but keep other protections). +bypass_actors: + - actor_id: 1 + actor_type: "OrganizationAdmin" + bypass_mode: "pull_request" diff --git a/.github/rulesets/03-release-hotfix.yml b/.github/rulesets/03-release-hotfix.yml new file mode 100644 index 0000000..362a35b --- /dev/null +++ b/.github/rulesets/03-release-hotfix.yml @@ -0,0 +1,18 @@ +name: "Release & Hotfix branch protection" +target: "branch" +# Start in evaluate mode to avoid blocking merges during rollout. +enforcement: "active" + +conditions: + ref_name: + include: + - "refs/heads/release/*" + - "refs/heads/hotfix/*" + exclude: [] + +rules: + - type: "non_fast_forward" + parameters: {} + + - type: "deletion" + parameters: {} diff --git a/.github/rulesets/04-tags.yml b/.github/rulesets/04-tags.yml new file mode 100644 index 0000000..ece9dc4 --- /dev/null +++ b/.github/rulesets/04-tags.yml @@ -0,0 +1,17 @@ +name: "Release tag protection (v*.*.*)" +target: "tag" +# Start in evaluate mode to avoid blocking tag operations during rollout. +enforcement: "active" + +conditions: + ref_name: + include: + - "refs/tags/v*.*.*" + exclude: [] + +rules: + - type: "non_fast_forward" + parameters: {} + + - type: "deletion" + parameters: {} diff --git a/.github/scripts/generate_settings_payloads.py b/.github/scripts/generate_settings_payloads.py new file mode 100644 index 0000000..6a68505 --- /dev/null +++ b/.github/scripts/generate_settings_payloads.py @@ -0,0 +1,92 @@ +import json +import pathlib +import sys +from typing import Any + +import yaml + +REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] +SETTINGS_FILE = REPO_ROOT / ".github" / "repository.settings.yml" +RULESETS_DIR = REPO_ROOT / ".github" / "rulesets" +OUT_RULESETS_DIR = REPO_ROOT / "ruleset_payloads" + + +def write_json(path: pathlib.Path, data: Any) -> None: + path.write_text(json.dumps(data, indent=2), encoding="utf-8") + + +def main() -> None: + # Defaults (safe no-op) + write_json(REPO_ROOT / "repo_patch.json", {}) + # IMPORTANT: topics are optional. Use JSON null to mean "do not touch topics". + write_json(REPO_ROOT / "topics.json", None) + write_json(REPO_ROOT / "security.json", {}) + + if SETTINGS_FILE.exists(): + settings = yaml.safe_load(SETTINGS_FILE.read_text(encoding="utf-8")) or {} + if not isinstance(settings, dict): + raise SystemExit("repository.settings.yml must be a mapping") + + patch_keys = { + "description", + "homepage", + "default_branch", + "has_issues", + "has_wiki", + "allow_squash_merge", + "allow_merge_commit", + "allow_rebase_merge", + "delete_branch_on_merge", + } + + repo_patch = { + k: settings.get(k) + for k in patch_keys + if k in settings and settings.get(k) is not None + } + + if "topics" in settings: + topics = settings.get("topics") + if topics is None: + topics = [] + if not isinstance(topics, list): + raise SystemExit("topics must be a list") + topics = [str(t).strip().lower() for t in topics if str(t).strip()] + write_json(REPO_ROOT / "topics.json", topics) + + security = settings.get("security", {}) or {} + if not isinstance(security, dict): + raise SystemExit("security must be a mapping") + + write_json(REPO_ROOT / "repo_patch.json", repo_patch) + write_json(REPO_ROOT / "security.json", security) + + OUT_RULESETS_DIR.mkdir(parents=True, exist_ok=True) + + if RULESETS_DIR.exists(): + for yml_path in sorted(RULESETS_DIR.glob("*.yml")): + data = yaml.safe_load(yml_path.read_text(encoding="utf-8")) or {} + if not isinstance(data, dict): + raise SystemExit(f"Ruleset {yml_path} must be a mapping") + + # Safety: never rely on hard-coded IDs from YAML + data.pop("id", None) + + # API compatibility: for some rule types, `parameters` is not allowed. + # Strip empty parameters blocks to avoid 422 "data matches no possible input". + rules = data.get("rules") + if isinstance(rules, list): + for rule in rules: + if isinstance(rule, dict) and rule.get("parameters") == {}: + rule.pop("parameters", None) + + out = OUT_RULESETS_DIR / f"{yml_path.stem}.json" + write_json(out, data) + + # Lightweight logs for debugging + sys.stdout.write(f"Prepared payloads in {REPO_ROOT}\n") + sys.stdout.write("Files: repo_patch.json, topics.json, security.json, ruleset_payloads/*.json\n") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/apply-settings-and-rulesets.yml b/.github/workflows/apply-settings-and-rulesets.yml new file mode 100644 index 0000000..c9f5928 --- /dev/null +++ b/.github/workflows/apply-settings-and-rulesets.yml @@ -0,0 +1,185 @@ +name: Apply Repo Settings & Rulesets (YAML) + +on: + push: + branches: + - develop + - main + paths: + - ".github/repository.settings.yml" + - ".github/rulesets/**" + - ".github/workflows/apply-settings-and-rulesets.yml" + workflow_dispatch: + inputs: + mode: + description: "dry-run prints payloads; apply updates repo/settings/rulesets" + required: true + default: "apply" + type: choice + options: + - dry-run + - apply + +permissions: + contents: read + actions: read + +jobs: + apply: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set mode + id: mode + shell: bash + run: | + MODE="dry-run" + + if [ "${{ github.event_name }}" = "push" ]; then + MODE="apply" + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + MODE="${{ inputs.mode }}" + fi + + echo "mode=$MODE" >> "$GITHUB_OUTPUT" + echo "Mode: $MODE" + + - name: Install Python tooling + shell: bash + run: | + python3 --version + python3 -m pip install --upgrade pip + python3 -m pip install pyyaml + + - name: Prepare payloads from YAML + id: prep + shell: bash + run: | + set -euo pipefail + python3 .github/scripts/generate_settings_payloads.py + + if [ -f .github/repository.settings.yml ]; then + echo "has_settings=true" >> "$GITHUB_OUTPUT" + else + echo "has_settings=false" >> "$GITHUB_OUTPUT" + fi + + if ls -1 ruleset_payloads/*.json >/dev/null 2>&1; then + echo "has_rulesets=true" >> "$GITHUB_OUTPUT" + else + echo "has_rulesets=false" >> "$GITHUB_OUTPUT" + fi + + - name: Dry-run summary + if: ${{ steps.mode.outputs.mode == 'dry-run' }} + shell: bash + run: | + echo "--- repo_patch.json ---"; cat repo_patch.json; echo + echo "--- topics.json ---"; cat topics.json; echo + echo "--- security.json ---"; cat security.json; echo + echo "--- rulesets ---"; ls -la ruleset_payloads || true + + - name: Validate GitHub token (repo admin) + if: ${{ steps.mode.outputs.mode == 'apply' }} + shell: bash + env: + GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }} + run: | + set -euo pipefail + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::GH_TOKEN is empty. Ensure the repo secret REPO_ADMIN_TOKEN exists (recommended) or GITHUB_TOKEN is available." + exit 1 + fi + + gh --version + + # Helpful debug: show classic PAT scopes (fine-grained tokens may show empty scopes) + OAUTH_SCOPES=$(gh api --include -H "Accept: application/vnd.github+json" user 2>/dev/null \ + | tr -d '\r' \ + | awk 'tolower($0) ~ /^x-oauth-scopes:/ {sub(/^[^:]*: /, ""); print; exit}') + if [ -n "${OAUTH_SCOPES:-}" ]; then + echo "Token OAuth scopes: ${OAUTH_SCOPES}" + else + echo "Token OAuth scopes: (not reported; likely fine-grained token)" + fi + + REPO="${{ github.repository }}" + IFS='/' read -r OWNER NAME <<< "$REPO" + + # Validate token can access this repository and has admin rights (required for PATCH repo settings and rulesets). + gh api -H "Accept: application/vnd.github+json" "repos/$OWNER/$NAME" --jq '.permissions' || true + HAS_ADMIN=$(gh api -H "Accept: application/vnd.github+json" "repos/$OWNER/$NAME" --jq '.permissions.admin // false' || echo "false") + if [ "$HAS_ADMIN" != "true" ]; then + echo "::error::Token does not have admin access to $REPO." + echo "::error::Fix: recreate REPO_ADMIN_TOKEN with classic scope 'repo' (and authorize SSO if your org enforces it), OR fine-grained PAT with repository access + Administration: Read and write." + exit 1 + fi + + # Extra validation: rulesets endpoint also requires admin access. + gh api -H "Accept: application/vnd.github+json" "repos/$OWNER/$NAME/rulesets" >/dev/null + + - name: Apply repository settings + if: ${{ steps.mode.outputs.mode == 'apply' && steps.prep.outputs.has_settings == 'true' }} + shell: bash + env: + GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + IFS='/' read -r OWNER NAME <<< "$REPO" + + if [ "$(jq 'length' repo_patch.json)" -gt 0 ]; then + gh api --method PATCH -H "Accept: application/vnd.github+json" \ + "repos/$OWNER/$NAME" --input repo_patch.json + fi + + if jq -e 'type=="array"' topics.json >/dev/null; then + python3 -c "import json; t=json.load(open('topics.json','r',encoding='utf-8')); json.dump({'names': t}, open('topics_payload.json','w',encoding='utf-8'))" + gh api --method PUT -H "Accept: application/vnd.github+json" \ + "repos/$OWNER/$NAME/topics" --input topics_payload.json + else + echo "Skipping topics update (not specified)." + fi + + if jq -e '.enable_vulnerability_alerts==true' security.json >/dev/null; then + gh api --method PUT -H "Accept: application/vnd.github+json" \ + "repos/$OWNER/$NAME/vulnerability-alerts" || true + fi + if jq -e '.enable_automated_security_fixes==true' security.json >/dev/null; then + gh api --method PUT -H "Accept: application/vnd.github+json" \ + "repos/$OWNER/$NAME/automated-security-fixes" || true + fi + + - name: Upsert repository rulesets + if: ${{ steps.mode.outputs.mode == 'apply' && steps.prep.outputs.has_rulesets == 'true' }} + shell: bash + env: + GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + IFS='/' read -r OWNER NAME <<< "$REPO" + + gh api -H "Accept: application/vnd.github+json" "repos/$OWNER/$NAME/rulesets" > existing_rulesets.json + + for payload in ruleset_payloads/*.json; do + RS_NAME=$(jq -r '.name // empty' "$payload") + if [ -z "$RS_NAME" ]; then + echo "Skipping $payload (missing .name)" + continue + fi + + RS_ID=$(jq -r --arg n "$RS_NAME" '.[] | select(.name==$n) | .id' existing_rulesets.json | head -n 1) + if [ -n "$RS_ID" ] && [ "$RS_ID" != "null" ]; then + echo "Updating ruleset: $RS_NAME (id=$RS_ID)" + gh api --method PUT -H "Accept: application/vnd.github+json" \ + "repos/$OWNER/$NAME/rulesets/$RS_ID" --input "$payload" + else + echo "Creating ruleset: $RS_NAME" + gh api --method POST -H "Accept: application/vnd.github+json" \ + "repos/$OWNER/$NAME/rulesets" --input "$payload" + fi + done diff --git a/.github/workflows/build-winccoa-image.yml b/.github/workflows/build-winccoa-image.yml new file mode 100644 index 0000000..63e35c7 --- /dev/null +++ b/.github/workflows/build-winccoa-image.yml @@ -0,0 +1,92 @@ +name: Build and Push WinCC OA Image + +on: + workflow_dispatch: + inputs: + node_version: + description: 'Node major version to install (default 20)' + required: false + default: '20' + repo_name: + description: 'Repository name used for image tags (default: repository name)' + required: false + default: '' + docker_namespace: + description: 'Docker namespace to push images to (default: repository owner)' + required: false + default: '' + +jobs: + build: + runs-on: ubuntu-latest + env: + NODE_MAJOR: ${{ github.event.inputs.node_version || '20' }} + WINCCOA_VERSION: 3.19.9 + REPO_NAME: ${{ github.event.inputs.repo_name != '' && github.event.inputs.repo_name || github.event.repository.name }} + DOCKER_NAMESPACE: ${{ github.event.inputs.docker_namespace != '' && github.event.inputs.docker_namespace || github.repository_owner }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + + + - name: Build image and load locally + uses: docker/build-push-action@v6 + with: + context: ./docker + file: ./docker/Dockerfile.winccoa-node + push: false + load: true + build-args: | + NODE_MAJOR=${{ env.NODE_MAJOR }} + REPO_NAME=${{ env.REPO_NAME }} + WINCCOA_VERSION=${{ env.WINCCOA_VERSION }} + tags: | + ${{ secrets.DOCKER_USER }}/${{ env.DOCKER_NAMESPACE }}-${{ env.REPO_NAME }}:winccoa-${{ env.WINCCOA_VERSION }}-node${{ env.NODE_MAJOR }}-${{ env.REPO_NAME }} + ${{ secrets.DOCKER_USER }}/${{ env.DOCKER_NAMESPACE }}-${{ env.REPO_NAME }}:${{ env.REPO_NAME }} + labels: | + repository=${{ env.REPO_NAME }} + winccoa=${{ env.WINCCOA_VERSION }} + node=${{ env.NODE_MAJOR }} + + - name: Run integration tests inside built image + env: + IMAGE_TAG: ${{ secrets.DOCKER_USER }}/${{ env.DOCKER_NAMESPACE }}-${{ env.REPO_NAME }}:winccoa-${{ env.WINCCOA_VERSION }}-node${{ env.NODE_MAJOR }}-${{ env.REPO_NAME }} + run: | + set -e + echo "Starting container from image $IMAGE_TAG" + docker run -d --name winccoa-image-test -v "$GITHUB_WORKSPACE":/workspace -e CI=true -w /workspace $IMAGE_TAG tail -f /dev/null + # Give the container a few seconds to start + sleep 3 + echo "Running tests inside container..." + docker exec --user root winccoa-image-test sh -c "xvfb-run -s '-screen 0 1280x1024x24' sh -c 'npm ci && npm test'" || { + echo "== Container logs =="; + docker logs winccoa-image-test || true; + docker rm -f winccoa-image-test || true; + exit 1; + } + echo "Tests passed in container" + docker rm -f winccoa-image-test || true + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Push image to Docker Hub + run: | + IMAGE_TAG=${{ secrets.DOCKER_USER }}/${{ env.DOCKER_NAMESPACE }}-${{ env.REPO_NAME }}:winccoa-${{ env.WINCCOA_VERSION }}-node${{ env.NODE_MAJOR }}-${{ env.REPO_NAME }} + docker push "$IMAGE_TAG" + docker tag "$IMAGE_TAG" "${{ secrets.DOCKER_USER }}/${{ env.DOCKER_NAMESPACE }}-${{ env.REPO_NAME }}:${{ env.REPO_NAME }}" + docker push "${{ secrets.DOCKER_USER }}/${{ env.DOCKER_NAMESPACE }}-${{ env.REPO_NAME }}:${{ env.REPO_NAME }}" + # If package.json specifies a canonical image name in config.winccoaImage, push that too + EXTRA_IMAGE=$(node -p "require('./package.json').config && require('./package.json').config.winccoaImage || ''") + if [ -n "$EXTRA_IMAGE" ]; then + echo "Tagging image as $EXTRA_IMAGE" + docker tag "$IMAGE_TAG" "$EXTRA_IMAGE" + docker push "$EXTRA_IMAGE" + else + echo "No extra image configured in package.json (config.winccoaImage)" + fi \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..f9278d2 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,208 @@ +name: CI/CD Pipeline + +on: + push: + branches: [develop, release/**] + pull_request: + branches: [main, develop] + +permissions: + contents: read + pull-requests: write + +jobs: + detect-extension-pack: + runs-on: ubuntu-latest + outputs: + is_extension_pack: ${{ steps.detect.outputs.is_extension_pack }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Detect Extension Pack + id: detect + shell: bash + run: | + set -euo pipefail + if [ ! -f package.json ]; then + echo "is_extension_pack=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + IS_EP=$(node -e "const p=require('./package.json'); const c=p.categories||[]; process.stdout.write(String(Array.isArray(c)&&c.includes('Extension Packs')));") + echo "is_extension_pack=$IS_EP" >> "$GITHUB_OUTPUT" + + lint: + needs: [detect-extension-pack] + if: ${{ needs.detect-extension-pack.outputs.is_extension_pack != 'true' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: "npm" + + - name: Install dependencies + run: npm ci + - name: Run linting + run: npm run lint && npm run lint:md + + format: + needs: [detect-extension-pack] + if: ${{ needs.detect-extension-pack.outputs.is_extension_pack != 'true' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run format check + run: npm run format:check + + test: + needs: [detect-extension-pack, format, lint] + if: ${{ needs.detect-extension-pack.outputs.is_extension_pack != 'true' }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + node-version: [20.x, 22.x, 24.x, 25.x] + + + env: + CI: true + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Compile + run: npm run compile + + # If your tests use @vscode/test-electron, Linux needs xvfb + - name: Run tests (Linux headless) + if: startsWith(matrix.os, 'ubuntu') + run: xvfb-run -a npm run test:unit + env: + CI: true + + - name: Run tests (non-Linux) + if: ${{ !startsWith(matrix.os, 'ubuntu') }} + run: npm run test:unit + env: + CI: true + + integration-winccoa: + needs: [detect-extension-pack, test] + if: ${{ needs.detect-extension-pack.outputs.is_extension_pack != 'true' && needs.test.result == 'success' }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + image: + - mpokornyetm/winccoa-tools-pack-vscode-winccoa-tests:winccoa-3.19.9-node20-vscode-winccoa-tests + + env: + WORKDIR: /workspace + IMAGE: ${{ matrix.image }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + shell: bash + env: + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + set -euo pipefail + if [ -n "${DOCKER_USER:-}" ] && [ -n "${DOCKER_PASSWORD:-}" ]; then + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USER" --password-stdin + else + echo "Docker credentials not available, attempting to pull public image" + fi + + - name: Pull WinCC OA image + shell: bash + run: docker pull "$IMAGE" + + - name: Run WinCC OA container + shell: bash + run: | + set -euo pipefail + docker run -d --name winccoa-ci \ + -v "$GITHUB_WORKSPACE":$WORKDIR:rw \ + -e CI=true \ + -w $WORKDIR \ + "$IMAGE" tail -f /dev/null + + - name: Show Node on runner + shell: bash + run: node -v || true + + - name: Wait for WinCC OA readiness + shell: bash + run: | + set -euo pipefail + echo "Waiting for WinCC OA inside container..." + for i in {1..60}; do + if docker exec winccoa-ci sh -c '[ -x /opt/WinCC_OA/bin/winccoa ]'; then + echo "Container ready (attempt $i)"; + break; + fi + sleep 2 + done + + - name: Run integration tests inside container + shell: bash + run: | + set -euo pipefail + docker exec --user root winccoa-ci sh -c ' + set -e + echo "Node version: $(node -v)" + echo "Starting integration tests under Xvfb" + xvfb-run -s "-screen 0 1280x1024x24" npm run ci:integration + ' + + - name: Collect logs on failure + if: failure() + shell: bash + run: | + echo "==== docker logs ====" + docker logs winccoa-ci || true + + - name: Stop and remove container + if: always() + shell: bash + run: | + docker rm -f winccoa-ci || true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 7bf6a05..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: CI - -on: - push: - branches: [ develop, main ] - pull_request: - branches: [ develop, main ] - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies (if any) - shell: bash - run: | - if [ -f package.json ]; then - npm ci || npm install - else - echo "No package.json found, skipping npm install" - fi - - - name: Optional package step (vsix) - shell: bash - run: | - if npm run package --silent >/dev/null 2>&1; then - npm run package - else - echo "No package script; attempting to create VSIX with vsce" - npm install -g @vscode/vsce || true - vsce package || echo "vsce package failed or not configured" - fi diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml new file mode 100644 index 0000000..d974945 --- /dev/null +++ b/.github/workflows/create-release-branch.yml @@ -0,0 +1,186 @@ +name: Create Release Branch + PR + +on: + workflow_dispatch: + inputs: + version: + description: "Release version (SemVer, e.g. 1.2.3)" + required: true + type: string + base_branch: + description: "Base branch (typically develop)" + required: true + default: "develop" + type: string + target_branch: + description: "PR target branch (typically main)" + required: true + default: "main" + type: string + draft: + description: "Create PR as draft" + required: true + default: false + type: boolean + labels: + description: "Comma-separated labels to apply (optional)" + required: false + default: "chore,release" + type: string + +concurrency: + group: create-release-branch-${{ github.repository }}-${{ github.event.inputs.version }} + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + create: + runs-on: ubuntu-latest + steps: + - name: Create release branch and PR + id: create + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }} + script: | + const rawVersion = (context.payload.inputs?.version || '').trim(); + const version = rawVersion.replace(/^v/i, ''); + const baseBranch = (context.payload.inputs?.base_branch || 'develop').trim(); + const targetBranch = (context.payload.inputs?.target_branch || 'main').trim(); + const draft = String(context.payload.inputs?.draft || 'false').toLowerCase() === 'true'; + const labelsRaw = (context.payload.inputs?.labels || '').trim(); + const labels = labelsRaw + ? labelsRaw.split(',').map(l => l.trim()).filter(Boolean) + : []; + + if (!/^\d+\.\d+\.\d+$/.test(version)) { + core.setFailed(`Invalid version '${rawVersion}'. Expected SemVer like 1.2.3 (optional leading 'v' allowed)`); + return; + } + + const releaseBranch = `release/v${version}`; + core.info(`Base: ${baseBranch}`); + core.info(`Target: ${targetBranch}`); + core.info(`Release branch: ${releaseBranch}`); + + // 1) Ensure base branch exists + let base; + try { + base = await github.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: baseBranch, + }); + } catch (error) { + core.setFailed(`Base branch '${baseBranch}' not found (or not accessible).`); + return; + } + + // 2) Create release branch if missing + let branchExists = false; + try { + await github.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: releaseBranch, + }); + branchExists = true; + core.info(`Branch already exists: ${releaseBranch}`); + } catch (error) { + if (error.status !== 404) throw error; + } + + if (!branchExists) { + const sha = base.data.commit.sha; + core.info(`Creating ${releaseBranch} at ${sha}`); + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/heads/${releaseBranch}`, + sha, + }); + } catch (error) { + const status = error?.status; + const msg = error?.message || String(error); + core.setFailed( + `Failed to create branch '${releaseBranch}'. This is usually caused by rulesets/branch protections blocking ref creation or missing token permissions. (HTTP ${status ?? 'unknown'}) ${msg}` + ); + return; + } + } + + // 3) Reuse existing open PR if present + const head = `${context.repo.owner}:${releaseBranch}`; + const existing = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + base: targetBranch, + head, + per_page: 10, + }); + + if (existing.data.length > 0) { + const pr = existing.data[0]; + core.info(`PR already exists: ${pr.html_url}`); + core.setOutput('branch', releaseBranch); + core.setOutput('pr_url', pr.html_url); + return; + } + + // 4) Create PR + const title = `chore(release): Release v${version}`; + const body = [ + `Automated release PR for **v${version}**.`, + '', + 'Checklist:', + '- [ ] CI passes on this branch', + '- [ ] Pre-release workflow produced a tested VSIX (GitHub pre-release asset)', + '- [ ] Manual validation done', + '- [ ] Merge into main when ready', + '', + 'Notes:', + `- Branch: ${releaseBranch}`, + `- Base: ${baseBranch}`, + `- Target: ${targetBranch}`, + ].join('\n'); + + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + head: releaseBranch, + base: targetBranch, + body, + draft, + }); + + core.info(`Created PR: ${pr.data.html_url}`); + + // 5) Apply labels if possible + if (labels.length > 0) { + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.data.number, + labels, + }); + core.info(`Applied labels: ${labels.join(', ')}`); + } catch (error) { + core.warning(`Could not apply labels (${labels.join(', ')}): ${error.message}`); + } + } + + core.setOutput('branch', releaseBranch); + core.setOutput('pr_url', pr.data.html_url); + + - name: Summary + run: | + echo "βœ… Release branch: ${{ steps.create.outputs.branch }}" + echo "πŸ”— PR: ${{ steps.create.outputs.pr_url }}" diff --git a/.github/workflows/gitflow-validation.yml b/.github/workflows/gitflow-validation.yml new file mode 100644 index 0000000..4aa3f0a --- /dev/null +++ b/.github/workflows/gitflow-validation.yml @@ -0,0 +1,167 @@ +name: Git Flow Validation + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - edited + - ready_for_review + - closed + +permissions: {} + +jobs: + validate-gitflow: + name: Validate Git Flow + Conventional Commits + runs-on: ubuntu-latest + if: github.event.action != 'closed' + permissions: + contents: read + pull-requests: read + steps: + - name: Validate branch + target + commits + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr) { + core.setFailed('This workflow only supports pull_request events.'); + return; + } + + const headRef = pr.head.ref; // e.g. feature/foo + const baseRef = pr.base.ref; // e.g. develop + + const rules = [ + { type: 'feature', prefixes: ['feature/'], expectedBase: 'develop' }, + { type: 'bugfix', prefixes: ['bugfix/'], expectedBase: 'develop' }, + { type: 'chore', prefixes: ['chore/'], expectedBase: 'develop', botOnly: true }, + { type: 'dependabot', prefixes: ['dependabot/'], expectedBase: 'develop', botOnly: true }, + { type: 'hotfix', prefixes: ['hotfix/'], expectedBase: 'main' }, + { type: 'release', prefixes: ['release/'], expectedBase: 'main' }, + ]; + + const matchingRule = rules.find(r => r.prefixes.some(p => headRef.startsWith(p))); + + const errors = []; + const warnings = []; + + if (!matchingRule) { + errors.push( + `Branch "${headRef}" does not follow Git Flow naming convention. ` + + `Expected one of: ${rules.flatMap(r => r.prefixes).join(', ')}` + ); + } else { + if (matchingRule.botOnly === true && pr.user && pr.user.type !== 'Bot') { + errors.push( + `Branch \"${headRef}\" is reserved for bot PRs (rule: ${matchingRule.type}). ` + + `PR author type is \"${pr.user.type}\".` + ); + } + + if (baseRef !== matchingRule.expectedBase) { + errors.push( + `Invalid target branch: ${matchingRule.type} branches must target "${matchingRule.expectedBase}", ` + + `but this PR targets "${baseRef}".` + ); + } + + if (matchingRule.type === 'feature' || matchingRule.type === 'bugfix') { + warnings.push('Reminder: feature/bugfix branches should be created from "develop" (cannot be validated reliably via GitHub API).'); + } + if (matchingRule.type === 'release') { + warnings.push('Reminder: release branches should be created from "develop" and then merged into "main" (branch source cannot be validated reliably via GitHub API).'); + } + if (matchingRule.type === 'hotfix') { + warnings.push('Reminder: hotfix branches should be created from "main" (branch source cannot be validated reliably via GitHub API).'); + } + } + + // Conventional Commits validation + // Squash merge uses the PR title as the final commit message. + // Validate the PR title instead of individual commits. + // Matches: type(scope)!: subject + // Example: feat(parser)!: support x + const conventionalRe = /^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([^\r\n()]+\))?(!)?:\s.+/; + + // Bot PRs (e.g. Dependabot) often use non-conventional commit messages. + // Allow them for dedicated bot-only branch prefixes. + const isBotPr = Boolean(pr.user && pr.user.type === 'Bot'); + const skipConventionalForBot = isBotPr && matchingRule && matchingRule.botOnly === true; + if (skipConventionalForBot) { + core.notice(`Skipping Conventional Commits validation for bot PR on ${matchingRule.type} branch (${headRef}).`); + return; + } + + const title = String(pr.title || '').trim(); + if (!title) { + errors.push('PR title is empty; expected a Conventional Commits style title (used for squash merge).'); + } else if (!conventionalRe.test(title)) { + errors.push( + `PR title does not follow Conventional Commits (used for squash merge). ` + + `Expected e.g. "feat(scope): subject" or "fix: subject". Got: "${title}".` + ); + } + + // Output warnings to log + for (const w of warnings) { + core.warning(w); + } + + if (errors.length > 0) { + core.setFailed(errors.join('\n\n')); + } else { + core.notice('Git Flow validation passed.'); + core.notice('Reminder: delete the branch after merge (feature/bugfix/release/hotfix).'); + } + + remind-branch-deletion: + name: Remind to delete merged branch + runs-on: ubuntu-latest + if: github.event.action == 'closed' && github.event.pull_request.merged == true + permissions: + pull-requests: write + contents: read + steps: + - name: Comment if branch still exists + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr) return; + + // Only meaningful for branches within the same repo. + if (pr.head.repo.full_name !== pr.base.repo.full_name) { + core.notice('PR is from a fork; skipping branch deletion reminder.'); + return; + } + + const { owner, repo } = context.repo; + const headRef = pr.head.ref; + + let branchExists = true; + try { + await github.rest.git.getRef({ owner, repo, ref: `heads/${headRef}` }); + } catch (e) { + branchExists = false; + } + + if (!branchExists) { + core.notice(`Branch ${headRef} is already deleted.`); + return; + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pr.number, + body: [ + '### πŸ” Git Flow Validation', + '', + '- [ ] Branch will be deleted after merge (for feature/hotfix/release branches)', + '', + `This PR was merged, but branch \`${headRef}\` still exists. Consider deleting it to keep the repo tidy.`, + ].join('\n'), + }); diff --git a/.github/workflows/gitflow.yml b/.github/workflows/gitflow.yml new file mode 100644 index 0000000..4d62545 --- /dev/null +++ b/.github/workflows/gitflow.yml @@ -0,0 +1,34 @@ +name: Gitflow Auto-Merge + +on: + workflow_run: + workflows: + - "Release" + types: + - completed + branches: + - main + +permissions: + contents: read + pull-requests: write + +jobs: + merge-back: + name: Gitflow + runs-on: ubuntu-latest + # Only run if the release workflow succeeded + if: github.event.workflow_run.conclusion == 'success' + + steps: + - name: Run gitflow action + uses: Logerfo/gitflow-action@0.0.5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + dev: develop + master: main + release: release + release-regex: '^release/(.*)' + label: gitflow + auto-merge: true + require-merge: false diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml new file mode 100644 index 0000000..a009645 --- /dev/null +++ b/.github/workflows/pr-labels.yml @@ -0,0 +1,23 @@ +name: PR Labels + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +permissions: + contents: read + pull-requests: write + issues: write +jobs: + label: + runs-on: ubuntu-latest + if: "${{ github.event.pull_request.head.repo.full_name == github.repository }}" + steps: + - name: Check out + uses: actions/checkout@v6 + + - name: Run labeler + uses: actions/labeler@v6 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/labeler.yml diff --git a/.github/workflows/pre-release-develop.yml b/.github/workflows/pre-release-develop.yml new file mode 100644 index 0000000..ef83ada --- /dev/null +++ b/.github/workflows/pre-release-develop.yml @@ -0,0 +1,57 @@ +name: Pre-Release (Beta) + +on: + workflow_run: + workflows: + - "CI/CD Pipeline" + types: + - completed + branches: + - develop + workflow_dispatch: + inputs: {} + +concurrency: + group: prerelease-beta-${{ github.event.workflow_run.head_sha || github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + packages: write + actions: write + checks: write + issues: write + +jobs: + pre-release: + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + uses: ./.github/workflows/prerelease-reusable.yml + with: + target_branch: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name }} + validate_release_branch: false + secrets: inherit + + cleanup-old-prereleases: + needs: [pre-release] + if: always() && needs.pre-release.result == 'success' + uses: winccoa-tools-pack/.github/.github/workflows/cleanup-prereleases-by-branch-reusable.yml@main + with: + target_branch: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name }} + keep_latest: 2 + secrets: inherit + + notify-success: + runs-on: ubuntu-latest + needs: [pre-release, cleanup-old-prereleases] + if: success() + + steps: + - name: Success Notification + run: | + echo "πŸ§ͺ Beta pre-release ${{ needs.pre-release.outputs.tag }} created successfully!" + echo "πŸ“¦ Tested VSIX attached to the GitHub pre-release" + echo "πŸ” Available for testing and validation" + echo "πŸ“ Auto-generated from develop" + echo "" + echo "πŸ”— Pre-release URL: https://github.com/${{ github.repository }}/releases/tag/${{ needs.pre-release.outputs.tag }}" diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index acfb75b..0a2e7d2 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -1,23 +1,20 @@ -name: Pre-Release (Develop) +name: Pre-Release (Alpha) on: - push: + pull_request: branches: - - develop - paths-ignore: - - "**.md" - - ".github/**" + - main + types: + - opened + - reopened + - synchronize + - ready_for_review workflow_dispatch: - inputs: - pre_release_type: - description: 'Pre-release type' - required: true - default: 'alpha' - type: choice - options: - - alpha - - beta - - rc + inputs: {} + +concurrency: + group: prerelease-alpha-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true permissions: contents: write @@ -28,178 +25,49 @@ permissions: issues: write jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Install dependencies (if any) - shell: bash - run: | - if [ -f package.json ]; then - npm ci || npm install - else - echo "No package.json found, skipping npm install" - fi + wait-required-workflows: + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + uses: winccoa-tools-pack/.github/.github/workflows/wait-for-workflows-reusable.yml@main + with: + sha: ${{ github.event.pull_request.head.sha }} + workflows: | + CI/CD Pipeline + PR Labels + Git Flow Validation + timeout_seconds: 1800 + poll_interval_seconds: 10 pre-release: - name: Create Pre-Release - runs-on: ubuntu-latest - needs: test - outputs: - version: ${{ steps.release.outputs.version }} - changelog: ${{ steps.changelog.outputs.changelog }} - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Use Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - shell: bash - run: | - npm ci || npm install - - - name: Configure Git - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" - - - name: Detect Pre-Release Type - id: detect-type - run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - echo "pre-release-type=${{ github.event.inputs.pre_release_type }}" >> $GITHUB_OUTPUT - else - RECENT_COMMITS=$(git log --oneline -10) - if echo "$RECENT_COMMITS" | grep -qi "beta\|release candidate\|rc"; then - PRE_RELEASE_TYPE=beta - elif echo "$RECENT_COMMITS" | grep -qi "release\|stable"; then - PRE_RELEASE_TYPE=rc - else - PRE_RELEASE_TYPE=alpha - fi - echo "pre-release-type=$PRE_RELEASE_TYPE" >> $GITHUB_OUTPUT - fi - - - name: Create Pre-Release Tag - id: release - run: | - PRE_RELEASE_TYPE="${{ steps.detect-type.outputs.pre-release-type }}" - CURRENT_VERSION=$(node -p "require('./package.json').version") - echo "Current version: $CURRENT_VERSION" - TIMESTAMP=$(date +%Y%m%d%H%M) - SHORT_SHA=$(git rev-parse --short HEAD) - npm version prerelease --preid="$PRE_RELEASE_TYPE.$TIMESTAMP.$SHORT_SHA" --no-git-tag-version - NEW_VERSION=$(node -p "require('./package.json').version") - echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "New pre-release version: $NEW_VERSION" - if git rev-parse "v$NEW_VERSION" >/dev/null 2>&1; then - git tag -d "v$NEW_VERSION" - fi - git tag "v$NEW_VERSION" - git add package.json - echo "Pre-release version updated" - - - name: Generate Pre-Release Changelog - id: changelog - run: | - VERSION="${{ steps.release.outputs.version }}" - echo "## πŸ§ͺ Pre-Release v$VERSION" > temp_changelog.md - echo "" >> temp_changelog.md - echo "**Pre-release from develop branch**" >> temp_changelog.md - echo "" >> temp_changelog.md - echo "### Recent Changes:" >> temp_changelog.md - LAST_TAG=$(git describe --tags --abbrev=0 --match="v*" 2>/dev/null || echo "") - if [ -n "$LAST_TAG" ]; then - git log $LAST_TAG..HEAD --pretty=format:"- %s (%h)" --no-merges >> temp_changelog.md - else - git log --pretty=format:"- %s (%h)" --no-merges -10 >> temp_changelog.md - fi - echo "" >> temp_changelog.md - echo "### πŸ” Testing Notes:" >> temp_changelog.md - echo "- This is a **pre-release** version from the develop branch" >> temp_changelog.md - echo "- Use for testing and validation purposes only" >> temp_changelog.md - echo "- Report issues at: https://github.com/${{ github.repository }}/issues" >> temp_changelog.md - CHANGELOG_CONTENT=$(cat temp_changelog.md) - { - echo "changelog<> $GITHUB_OUTPUT - rm temp_changelog.md - - - name: Push Pre-Release Tag - run: | - VERSION="${{ steps.release.outputs.version }}" - if git ls-remote --tags origin "v$VERSION" | grep -q "v$VERSION"; then - git push --force origin "v$VERSION" - else - git push origin "v$VERSION" - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Package Extension - run: | - npm install -g @vscode/vsce - vsce package - - - name: Upload VSIX Artifact - uses: actions/upload-artifact@v5 - with: - name: pre-release-${{ steps.release.outputs.version }} - path: "*.vsix" - retention-days: 14 - - - name: Create GitHub Pre-Release - uses: softprops/action-gh-release@v2 - with: - tag_name: "v${{ steps.release.outputs.version }}" - name: "πŸ§ͺ Pre-Release v${{ steps.release.outputs.version }}" - target_commitish: develop - body: | - ${{ steps.changelog.outputs.changelog }} - files: "*.vsix" - draft: false - prerelease: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Delete Old Pre-Releases - run: | - echo "Cleaning up old pre-releases..." - PRERELEASES=$(gh release list --limit 100 --repo ${{ github.repository }} --json tagName,isPrerelease,createdAt --jq '[.[] | select(.isPrerelease == true)] | sort_by(.createdAt) | reverse | .[].tagName') - OLD_PRERELEASES=$(echo "$PRERELEASES" | tail -n +2) - if [ -z "$OLD_PRERELEASES" ]; then - echo "No old pre-releases to delete" - else - for TAG in $OLD_PRERELEASES; do - echo "Deleting $TAG" - gh release delete "$TAG" --yes --repo ${{ github.repository }} --cleanup-tag || echo "Failed to delete $TAG" - done - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + needs: [wait-required-workflows] + if: ${{ always() && (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && needs.wait-required-workflows.result == 'success')) }} + uses: ./.github/workflows/prerelease-reusable.yml + with: + target_branch: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }} + validate_release_branch: ${{ github.event_name == 'pull_request' && startsWith(github.head_ref, 'release/v') }} + secrets: inherit + + cleanup-old-prereleases: + needs: [pre-release] + if: always() && needs.pre-release.result == 'success' + uses: winccoa-tools-pack/.github/.github/workflows/cleanup-prereleases-by-branch-reusable.yml@main + with: + target_branch: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }} + keep_latest: 1 + secrets: inherit notify-success: runs-on: ubuntu-latest - needs: [pre-release] + needs: [pre-release, cleanup-old-prereleases] if: success() + steps: - name: Success Notification run: | - echo "Pre-release v${{ needs.pre-release.outputs.version }} created successfully!" - echo "URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.pre-release.outputs.version }}" + echo "πŸ§ͺ Alpha pre-release ${{ needs.pre-release.outputs.tag }} created successfully!" + echo "πŸ“¦ Tested VSIX attached to the GitHub pre-release" + echo "πŸ” Available for testing and validation" + echo "πŸ“ Auto-generated from a main-target PR (release/hotfix)" + echo "" + echo "πŸ”— Pre-release URL: https://github.com/${{ github.repository }}/releases/tag/${{ needs.pre-release.outputs.tag }}" + echo "" + echo "πŸ§ͺ Test the pre-release and provide feedback!" diff --git a/.github/workflows/prerelease-reusable.yml b/.github/workflows/prerelease-reusable.yml new file mode 100644 index 0000000..455c7fa --- /dev/null +++ b/.github/workflows/prerelease-reusable.yml @@ -0,0 +1,113 @@ +name: Reusable Action Pre-Release + +on: + workflow_call: + inputs: + target_branch: + description: "Branch that triggered prerelease (e.g. release/v1.2.3)" + required: true + type: string + validate_release_branch: + description: "If true: require target_branch to be release/vX.Y.Z and match the computed version" + required: false + default: true + type: boolean + outputs: + version: + description: "Computed prerelease version (x.y.z-)" + value: ${{ jobs.prerelease.outputs.version }} + tag: + description: "Computed prerelease tag (v)" + value: ${{ jobs.prerelease.outputs.tag }} + +permissions: + contents: write + +concurrency: + group: release-pipeline-${{ github.repository }} + cancel-in-progress: false + +jobs: + versioning: + uses: winccoa-tools-pack/.github/.github/workflows/versioning-tags-changelog-reusable.yml@main + with: + mode: prerelease + target_branch: ${{ inputs.target_branch }} + validate_release_branch: ${{ inputs.validate_release_branch }} + push_tag: true + force_push_tag: true + secrets: inherit + + prerelease: + runs-on: ubuntu-latest + needs: [versioning] + outputs: + version: ${{ steps.meta.outputs.version }} + tag: ${{ steps.meta.outputs.tag }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ inputs.target_branch != '' && inputs.target_branch || github.sha }} + + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Install tools + run: npm install -g @vscode/vsce + + - name: Set prerelease metadata + id: meta + shell: bash + run: | + TAG="${{ needs.versioning.outputs.tag }}" + VERSION="${TAG#v}" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Build release body + id: body + shell: bash + run: | + { + echo "body<> "$GITHUB_OUTPUT" + + - name: Package extension (VSIX) + run: vsce package + + - name: Upload VSIX artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ format('pre-release-{0}', github.sha) }} + path: "*.vsix" + retention-days: 14 + + - name: Create GitHub prerelease + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.meta.outputs.tag }} + name: ${{ format('πŸ§ͺ Pre-Release {0}', steps.meta.outputs.tag) }} + target_commitish: ${{ inputs.target_branch != '' && inputs.target_branch || github.sha }} + body: ${{ steps.body.outputs.body }} + files: "*.vsix" + draft: false + prerelease: true + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-reusable.yml b/.github/workflows/release-reusable.yml new file mode 100644 index 0000000..2c5172b --- /dev/null +++ b/.github/workflows/release-reusable.yml @@ -0,0 +1,164 @@ +name: Release (Reusable) + +on: + workflow_call: + inputs: + target_branch: + description: "Stable branch to release from (usually main)" + required: true + type: string + outputs: + version: + description: "Released version (x.y.z)" + value: ${{ jobs.versioning.outputs.version }} + tag: + description: "Release tag (vX.Y.Z)" + value: ${{ jobs.versioning.outputs.tag }} + +permissions: + contents: write + +concurrency: + group: release-pipeline-${{ github.repository }} + cancel-in-progress: false + +jobs: + versioning: + uses: winccoa-tools-pack/.github/.github/workflows/versioning-tags-changelog-reusable.yml@main + with: + mode: release + create_tag: false + push_tag: false + secrets: inherit + + precheck: + runs-on: ubuntu-latest + needs: [versioning] + outputs: + prerelease_tag: ${{ steps.find.outputs.prerelease_tag }} + steps: + - name: Find matching prerelease for this version + id: find + shell: bash + run: | + VERSION="${{ needs.versioning.outputs.version }}" + PREFIX="v$VERSION-" + REPO="${GITHUB_REPOSITORY}" + + echo "πŸ”Ž Looking for prerelease tags starting with: $PREFIX" + + PRERELEASE_TAG=$(gh release list --repo "$REPO" --limit 200 \ + --json tagName,isPrerelease,createdAt \ + --jq "[.[] | select(.isPrerelease == true and (.tagName | startswith(\"$PREFIX\"))) ] | sort_by(.createdAt) | reverse | .[0].tagName" 2>/dev/null) + + if [ -z "$PRERELEASE_TAG" ] || [ "$PRERELEASE_TAG" = "null" ]; then + echo "❌ ERROR: No matching prerelease found for version $VERSION (expected tag like v$VERSION-)" + echo "Hint: run prerelease workflow on release/v$VERSION first." + exit 1 + fi + + echo "βœ… Found prerelease tag: $PRERELEASE_TAG" + echo "prerelease_tag=$PRERELEASE_TAG" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + tag: + uses: winccoa-tools-pack/.github/.github/workflows/versioning-tags-changelog-reusable.yml@main + needs: [precheck] + with: + mode: release + create_tag: true + push_tag: true + force_push_tag: false + secrets: inherit + + release: + runs-on: ubuntu-latest + needs: [versioning, precheck, tag] + steps: + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: Install tools + run: npm install -g @vscode/vsce + + - name: Download tested VSIX from prerelease + shell: bash + run: | + mkdir -p dist + PRERELEASE_TAG="${{ needs.precheck.outputs.prerelease_tag }}" + REPO="${GITHUB_REPOSITORY}" + + gh release download --repo "$REPO" "$PRERELEASE_TAG" --pattern "*.vsix" --dir dist + + VSIX_PATH=$(ls -1 dist/*.vsix 2>/dev/null | head -n 1) + if [ -z "$VSIX_PATH" ]; then + echo "❌ ERROR: No .vsix found in prerelease assets for $PRERELEASE_TAG" + ls -la dist || true + exit 1 + fi + + echo "πŸ“¦ Downloaded VSIX: $VSIX_PATH" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish to VS Code Marketplace (if configured) + shell: bash + run: | + VSIX_PATH=$(ls -1 dist/*.vsix 2>/dev/null | head -n 1) + if [ -z "$VSIX_PATH" ]; then + echo "❌ ERROR: No .vsix found in dist/" + exit 1 + fi + + if [ -n "${{ secrets.VSCE_PAT }}" ]; then + echo "πŸͺ Publishing tested VSIX to VS Code Marketplace..." + vsce publish --packagePath "$VSIX_PATH" -p "${{ secrets.VSCE_PAT }}" + echo "βœ… Published to marketplace successfully!" + else + echo "⚠️ VSCE_PAT secret not found - skipping marketplace publish" + fi + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.tag.outputs.tag }} + name: ${{ format('Release {0}', needs.tag.outputs.tag) }} + target_commitish: ${{ github.sha }} + body: | + ${{ needs.versioning.outputs.changelog }} + + ### βœ… Tested Artifact + - Published from tested pre-release asset: `${{ needs.precheck.outputs.prerelease_tag }}` + files: "dist/*.vsix" + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Cleanup prereleases for this version + shell: bash + run: | + VERSION="${{ needs.versioning.outputs.version }}" + PREFIX="v$VERSION-" + REPO="${GITHUB_REPOSITORY}" + + echo "🧹 Deleting prereleases with tag prefix: $PREFIX" + + TAGS=$(gh release list --repo "$REPO" --limit 200 --json tagName,isPrerelease \ + --jq "[.[] | select(.isPrerelease == true and (.tagName | startswith(\"$PREFIX\"))) ] | .[].tagName") + + if [ -z "$TAGS" ]; then + echo "βœ… No prereleases to delete" + exit 0 + fi + + for TAG in $TAGS; do + echo " - Deleting $TAG" + gh release delete --repo "$REPO" "$TAG" --yes --cleanup-tag || echo "⚠️ Failed to delete $TAG" + done + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbc0a22..7ac08c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,16 @@ name: Release on: - push: - branches: [main] + workflow_run: + workflows: + - "CI/CD Pipeline" + - "Integration Tests - WinCC OA" + types: + - completed + branches: + - main + workflow_dispatch: + inputs: {} permissions: contents: write @@ -13,200 +21,26 @@ permissions: issues: write jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Install dependencies (if any) - shell: bash - run: | - if [ -f package.json ]; then - npm ci || npm install - else - echo "No package.json found, skipping npm install" - fi - release: - needs: test - runs-on: ubuntu-latest - if: github.event_name == 'push' - outputs: - version: ${{ steps.release.outputs.version }} - changelog: ${{ steps.changelog.outputs.changelog }} - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Use Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - shell: bash - run: | - npm ci || npm install - - - name: Configure Git - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" - - - name: Install standard-version - run: npm install -g standard-version - - # PR label detection and workflow_dispatch inputs removed - - - name: Validate Version Consistency - id: version-check - run: | - CURRENT_VERSION=$(node -p "require('./package.json').version") - if [[ "$CURRENT_VERSION" == *"-"* ]]; then - BASE_VERSION=$(echo "$CURRENT_VERSION" | cut -d'-' -f1) - npm version "$BASE_VERSION" --no-git-tag-version - CLEAN_VERSION=$(node -p "require('./package.json').version") - else - CLEAN_VERSION="$CURRENT_VERSION" - fi - echo "clean-version=$CLEAN_VERSION" >> $GITHUB_OUTPUT - - - name: Create Release - id: release - run: | - CURRENT_VERSION="${{ steps.version-check.outputs.clean-version }}" - # For push-based releases always run standard-version to compute next version - standard-version --no-verify --skip.commit=true --skip.tag=true - NEW_VERSION=$(node -p "require('./package.json').version") - echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - git tag "v$NEW_VERSION" - if ! git diff --staged --quiet || ! git diff --quiet; then - echo "has-changes=true" >> $GITHUB_OUTPUT - else - echo "has-changes=false" >> $GITHUB_OUTPUT - fi - - - name: Extract Changelog - id: changelog - run: | - VERSION=$(node -p "require('./package.json').version") - CHANGELOG_CONTENT=$(awk '/^## \['"$VERSION"'\]/{flag=1; next} /^## \[/ {flag=0} flag' CHANGELOG.md || true) - if [ -z "$CHANGELOG_CONTENT" ]; then - CHANGELOG_CONTENT=$(awk '/^## '"$VERSION"'/{flag=1; next} /^## /{flag=0} flag' CHANGELOG.md || true) - fi - if [ -z "$CHANGELOG_CONTENT" ]; then - CHANGELOG_CONTENT="Changes in version $VERSION" - fi - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create PR for version changes - if: steps.release.outputs.has-changes == 'true' - run: | - VERSION="${{ steps.release.outputs.version }}" - BRANCH_NAME="release/v$VERSION" - git checkout -b "$BRANCH_NAME" - git add package.json CHANGELOG.md || true - printf "chore(release): %s\n\n- Update version to %s\n- Update CHANGELOG.md with release notes\n\n[skip ci]\n" "$VERSION" "$VERSION" > /tmp/commitmsg - git commit -F /tmp/commitmsg || echo "No changes to commit" - if git push origin "$BRANCH_NAME"; then - if command -v gh &> /dev/null; then - gh pr create --title "chore(release): Release v$VERSION" --body "Automated release PR for v$VERSION" --label "chore,release" --head "$BRANCH_NAME" --base main || echo "Failed to create PR via CLI" - else - echo "Branch pushed. Please create PR manually for version updates." - fi - else - echo "Failed to push branch $BRANCH_NAME" - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Push tag only - run: | - NEW_TAG="v${{ steps.release.outputs.version }}" - git push "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" "$NEW_TAG" || echo "Failed to push tag" - - - name: Package Extension - run: | - npm install -g @vscode/vsce - vsce package - - - name: Upload VSIX Artifact - uses: actions/upload-artifact@v5 - with: - name: extension-${{ steps.release.outputs.version }} - path: "*.vsix" - retention-days: 30 - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: "v${{ steps.release.outputs.version }}" - name: "Release v${{ steps.release.outputs.version }}" - target_commitish: ${{ github.sha }} - body: | - ## πŸš€ Release v${{ steps.release.outputs.version }} - - ${{ steps.changelog.outputs.changelog }} - files: "*.vsix" - draft: false - prerelease: false - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-marketplace: - needs: [test, release] - runs-on: ubuntu-latest - if: success() - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Use Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Publish to VS Code Marketplace - run: | - npm install -g @vscode/vsce - PACKAGE_VERSION=$(node -p "require('./package.json').version") - if [[ ! "$PACKAGE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Invalid version format for marketplace: $PACKAGE_VERSION" - exit 1 - fi - vsce package - if [ -n "${{ secrets.VSCE_PAT }}" ]; then - vsce publish $PACKAGE_VERSION -p ${{ secrets.VSCE_PAT }} - else - echo "VSCE_PAT not found - skipping marketplace publish" - fi - env: - VSCE_PAT: ${{ secrets.VSCE_PAT }} + # Only run if the triggering workflow succeeded or manual dispatch + if: | + github.event_name == 'workflow_dispatch' || + github.event.workflow_run.conclusion == 'success' + uses: ./.github/workflows/release-reusable.yml + with: + target_branch: main + secrets: inherit notify-success: - needs: [release, publish-marketplace] runs-on: ubuntu-latest + needs: [release] if: success() + steps: - name: Success Notification run: | - echo "Release v${{ needs.release.outputs.version }} completed successfully!" - echo "URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.release.outputs.version }}" + echo "πŸŽ‰ Release ${{ needs.release.outputs.tag }} completed successfully!" + echo "πŸ“¦ Tested VSIX attached to the GitHub Release" + echo "πŸͺ Extension published to VS Code Marketplace (if configured)" + echo "" + echo "πŸ”— Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ needs.release.outputs.tag }}" diff --git a/.github/workflows/required-checks.yml b/.github/workflows/required-checks.yml new file mode 100644 index 0000000..4b6641f --- /dev/null +++ b/.github/workflows/required-checks.yml @@ -0,0 +1,31 @@ +name: Required Checks + +on: + pull_request: + branches: + - main + - develop + types: + - opened + - reopened + - synchronize + - ready_for_review + - edited + +permissions: + actions: read + +jobs: + gate: + name: Gate + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + uses: winccoa-tools-pack/.github/.github/workflows/wait-for-workflows-reusable.yml@main + with: + sha: ${{ github.event.pull_request.head.sha }} + workflows: | + CI/CD Pipeline + PR Labels + Git Flow Validation + timeout_seconds: 1800 + poll_interval_seconds: 10 + secrets: inherit diff --git a/.github/workflows/setup-gitflow.yml b/.github/workflows/setup-gitflow.yml new file mode 100644 index 0000000..0b50e47 --- /dev/null +++ b/.github/workflows/setup-gitflow.yml @@ -0,0 +1,153 @@ +name: Setup Git Flow Branch Protection + +on: + workflow_dispatch: + inputs: + apply_protection: + description: "Apply branch protection rules" + required: true + default: true + type: boolean + +permissions: + contents: read + repository-projects: write + issues: write + pull-requests: write + +jobs: + setup-gitflow-protection: + runs-on: ubuntu-latest + if: github.event.inputs.apply_protection == 'true' + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup branch protection for main + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + try { + await github.rest.repos.updateBranchProtection({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: 'main', + required_status_checks: { + strict: true, + contexts: ['test (20.x)', 'package'] + }, + enforce_admins: false, + required_pull_request_reviews: { + required_approving_review_count: 1, + dismiss_stale_reviews: false, + require_code_owner_reviews: false, + require_last_push_approval: false + }, + restrictions: null, + allow_force_pushes: false, + allow_deletions: false, + block_creations: false, + required_conversation_resolution: false, + required_linear_history: false + }); + console.log('βœ… Main branch protection updated successfully'); + } catch (error) { + console.error('❌ Failed to update main branch protection:', error.message); + throw error; + } + + - name: Setup branch protection for develop + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + try { + await github.rest.repos.updateBranchProtection({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: 'develop', + required_status_checks: { + strict: true, + contexts: ['test (20.x)', 'package'] + }, + enforce_admins: false, + required_pull_request_reviews: { + required_approving_review_count: 1, + dismiss_stale_reviews: false, + require_code_owner_reviews: false, + require_last_push_approval: false + }, + restrictions: null, + allow_force_pushes: true, + allow_deletions: false, + block_creations: false, + required_conversation_resolution: false, + required_linear_history: false + }); + console.log('βœ… Develop branch protection updated successfully'); + } catch (error) { + console.error('❌ Failed to update develop branch protection:', error.message); + throw error; + } + + - name: Setup Git Flow branch naming rules + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const branchPatterns = ['feature/**','release/**','hotfix/**','main','develop']; + console.log('πŸ“‹ Git Flow branch patterns configured:'); + branchPatterns.forEach(pattern => { console.log(` - ${pattern}`); }); + console.log('ℹ️ Manual branch naming enforcement should be implemented through:'); + console.log(' 1. Team conventions and training'); + console.log(' 2. Pull request templates'); + console.log(' 3. Git hooks (if desired)'); + + validate-gitflow-setup: + runs-on: ubuntu-latest + needs: setup-gitflow-protection + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Validate Git Flow branches exist + run: | + echo "πŸ” Validating Git Flow branch setup..." + if git ls-remote --heads origin main | grep -q main; then + echo "βœ… Main branch exists" + else + echo "❌ Main branch missing" + exit 1 + fi + if git ls-remote --heads origin develop | grep -q develop; then + echo "βœ… Develop branch exists" + else + echo "❌ Develop branch missing" + exit 1 + fi + echo "πŸŽ‰ Git Flow branch structure validated successfully!" + + - name: Display Git Flow setup summary + run: | + echo "πŸ“Š Git Flow Setup Summary" + echo "========================" + echo "\n🌳 Branch Structure:" + echo " main - Production ready code" + echo " develop - Integration branch for development" + echo "\nπŸ›‘οΈ Protection Rules Applied:" + echo " βœ… Required pull request reviews (1+ reviewer)" + echo " βœ… Required status checks (CI/CD)" + echo " βœ… Up-to-date branch requirements" + echo " βœ… No force pushes to main" + echo "\nπŸ“ Branch Naming Conventions:" + echo " feature/description - New features" + echo " release/version - Release preparation" + echo " hotfix/version - Critical fixes" + echo "\nπŸ“– Documentation:" + echo " See docs/GITFLOW_WORKFLOW.md for complete guide" + echo "\nπŸš€ Next Steps:" + echo " 1. Review Git Flow documentation" + echo " 2. Create feature branches using: git checkout -b feature/my-feature develop" + echo " 3. Follow pull request process for all merges" + echo " 4. Use release branches for version preparation" diff --git a/.github/workflows/sync-org-files.yml b/.github/workflows/sync-org-files.yml new file mode 100644 index 0000000..bd208d6 --- /dev/null +++ b/.github/workflows/sync-org-files.yml @@ -0,0 +1,38 @@ + +name: Sync organization files to repositories + +on: + schedule: + - cron: "15 6 * * 1-5" + workflow_dispatch: + +# Top‑level permissions β€” required for modifying workflow files +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + sync: + uses: winccoa-tools-pack/.github/.github/workflows/template-sync-reusable.yml@main + with: + template_repo: "winccoa-tools-pack/.github" + template_ref: "main" + sync_mode: "update" + paths: | + .github/workflows/sync-org-files.yml + .github/workflows/apply-settings-and-rulesets.yml + .github/scripts/generate_settings_payloads.py + .github/repository.settings.yml + .github/rulesets/**/*.yml + SECURITY.md + FUNDING.yml + LICENSE + # Add more paths you want from the org .github repo + pr_title: "chore(template): sync gh org files to repository" + pr_body: "Automated sync from organisation repository." + pr_labels: "sync, automated" + # pr_reviewers: "REPLACE_WITH_REAL_USERS_OR_TEAMS" + branch_prefix: "chore/org-sync" + secrets: + token: ${{ secrets.TEMPLATE_SYNC_PAT }} \ No newline at end of file diff --git a/.github/workflows/sync-template-files.yml b/.github/workflows/sync-template-files.yml new file mode 100644 index 0000000..bec1142 --- /dev/null +++ b/.github/workflows/sync-template-files.yml @@ -0,0 +1,38 @@ +name: Sync template files to repositories + +on: + schedule: + - cron: "15 6 * * 1-5" + workflow_dispatch: + +# Top‑level permissions β€” required for modifying workflow files +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + sync: + # Correct reference and use a valid ref (main or a tag) + uses: winccoa-tools-pack/.github/.github/workflows/template-sync-reusable.yml@main + with: + template_repo: "winccoa-tools-pack/template-vscode-extension" + template_ref: "main" + sync_mode: "update" + paths: | + .github/ISSUE_TEMPLATE/**/*.yml + .github/PULL_REQUEST_TEMPLATE/**/*.md + .github/workflows/**/*.yml + .github/labeler.yml + .github/rulesets/**/*.yml + .github/dependabot.yml + .github/DISCUSSIONS.md +# .github/ISSUE_TEMPLATE/**/*.yaml + pr_title: "chore(template): sync template files" + pr_body: "Automated sync from template repository." + pr_labels: "sync, automated" + # pr_reviewers: "REPLACE_WITH_REAL_USERS_OR_TEAMS" + branch_prefix: "chore/template-sync" + + secrets: + token: ${{ secrets.TEMPLATE_SYNC_PAT }} diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..e205b18 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +buy_me_a_coffee: mPokornyETM diff --git a/LICENSE b/LICENSE index 8ee6697..cb00e19 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 mPokornyETM +Copyright (c) 2026 winccoa-tools-pack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..02d52bb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you discover a security vulnerability in any project within the WinCC OA Tools Pack, thank you for reporting it responsibly. + +## Reporting + +- **Preferred:** Open a confidential report by emailing (replace with your org email) with a clear subject and steps to reproduce. Attach logs, versions and an impact assessment if available. +- **Alternative:** If you cannot email, create a new issue labeled `security` in this repository. Note that private or restricted issues require organization settings; maintainers will triage and move sensitive details to a private channel if needed. + +**Important:** Replace the placeholder email address `` with your real security contact before publishing this repository or copying this `SECURITY.md` into other projects. + +## Response policy + +- We will acknowledge reports within 3 business days. +- Critical vulnerabilities will receive an initial response and mitigation plan within 5 business days. +- We aim to publish fixes within a reasonable timeframe and will coordinate public disclosure with the reporter. + +## Disclosure + +- Coordinated disclosure is preferred. Do not publicly disclose the vulnerability until a fix has been released or an agreed timeline has elapsed. +- If you are a security researcher, include contact details and allow the maintainers reasonable time to respond. + +## Patches and mitigations + +- When a patch is available, it will be published in the repository with a security advisory and release notes. +- We will backport fixes to supported branches where feasible. + +## Acknowledgements + +- Reporters who follow responsible disclosure may be acknowledged in the project security advisory and/or `SECURITY.md`, unless they request anonymity. diff --git a/package-lock.json b/package-lock.json index 867c34d..9557da7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wincc-oa-tools-pack", - "version": "0.0.3", + "version": "0.0.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wincc-oa-tools-pack", - "version": "0.0.3", + "version": "0.0.6", "license": "MIT", "engines": { "vscode": "^1.60.0" diff --git a/package.json b/package.json index 7ef36b3..8b74720 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "wincc-oa-tools-pack", "displayName": "WinCC OA Tools Pack", "publisher": "mPokornyETM", - "version": "0.0.4", + "version": "0.0.6", "engines": { "vscode": "^1.60.0" }, From 22b8fcbfe4142c9074c75082972f802751c06579 Mon Sep 17 00:00:00 2001 From: Martin Pokorny <89339813+mPokornyETM@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:22:04 +0100 Subject: [PATCH 03/10] (chore) Hotfix trigger release (#14) Trigger release on pushes into main --- .github/workflows/release.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ac08c5..f04dc65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,11 @@ name: Release on: - workflow_run: - workflows: - - "CI/CD Pipeline" - - "Integration Tests - WinCC OA" - types: - - completed + pull_request: branches: - main + types: + - closed workflow_dispatch: inputs: {} @@ -22,10 +19,18 @@ permissions: jobs: release: - # Only run if the triggering workflow succeeded or manual dispatch + # Only run for merged release/hotfix PRs, or manual dispatch if: | github.event_name == 'workflow_dispatch' || - github.event.workflow_run.conclusion == 'success' + ( + github.event_name == 'pull_request' && + github.event.pull_request.merged == true && + ( + startsWith(github.head_ref, 'release/') || + startsWith(github.head_ref, 'hotfix/') || + contains(github.event.pull_request.labels.*.name, 'release') + ) + ) uses: ./.github/workflows/release-reusable.yml with: target_branch: main From ff65a6152a143b43146e2f0ae7dbdd15019c0b99 Mon Sep 17 00:00:00 2001 From: Martin Pokorny <89339813+mPokornyETM@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:33:04 +0100 Subject: [PATCH 04/10] chore(release): Release v0.0.7 (#19) Update dependencies --- .github/rulesets/01-develop.yml | 4 +- .github/rulesets/02-main.yml | 4 +- .../workflows/apply-settings-and-rulesets.yml | 2 +- .github/workflows/ci-cd.yml | 61 ++++++++++++++++--- .github/workflows/gitflow-validation.yml | 17 ++++++ .github/workflows/pr-labels.yml | 17 ++++++ .github/workflows/prerelease-reusable.yml | 6 +- .github/workflows/release-reusable.yml | 2 +- .github/workflows/release.yml | 21 +++---- package-lock.json | 4 +- package.json | 2 +- 11 files changed, 109 insertions(+), 31 deletions(-) diff --git a/.github/rulesets/01-develop.yml b/.github/rulesets/01-develop.yml index 462c91a..1970ac5 100644 --- a/.github/rulesets/01-develop.yml +++ b/.github/rulesets/01-develop.yml @@ -28,7 +28,9 @@ rules: parameters: strict_required_status_checks_policy: true required_status_checks: - - context: "Required Checks / Gate / wait" + - context: "CI/CD Pipeline - Required" + - context: "PR Labels - Required" + - context: "Git Flow Validation - Required" - type: "required_linear_history" parameters: {} diff --git a/.github/rulesets/02-main.yml b/.github/rulesets/02-main.yml index 9c30b51..9e18cc7 100644 --- a/.github/rulesets/02-main.yml +++ b/.github/rulesets/02-main.yml @@ -28,7 +28,9 @@ rules: parameters: strict_required_status_checks_policy: true required_status_checks: - - context: "Required Checks / Gate / wait" + - context: "CI/CD Pipeline - Required" + - context: "PR Labels - Required" + - context: "Git Flow Validation - Required" - type: "required_linear_history" parameters: {} diff --git a/.github/workflows/apply-settings-and-rulesets.yml b/.github/workflows/apply-settings-and-rulesets.yml index c9f5928..a1bfb62 100644 --- a/.github/workflows/apply-settings-and-rulesets.yml +++ b/.github/workflows/apply-settings-and-rulesets.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set mode id: mode diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f9278d2..b0cd03e 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Detect Extension Pack id: detect @@ -40,10 +40,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Use Node.js 20.x - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20.x cache: "npm" @@ -60,10 +60,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Use Node.js 20.x - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20.x cache: "npm" @@ -90,10 +90,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -134,7 +134,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -206,3 +206,48 @@ jobs: shell: bash run: | docker rm -f winccoa-ci || true + + required: + name: CI/CD Pipeline - Required + runs-on: ubuntu-latest + if: ${{ always() }} + needs: + - detect-extension-pack + - lint + - format + - test + - integration-winccoa + steps: + - name: Verify required jobs + shell: bash + run: | + set -euo pipefail + + # Treat skipped as OK (e.g. extension pack repos) + RESULTS=( + "detect-extension-pack:${{ needs.detect-extension-pack.result }}" + "lint:${{ needs.lint.result }}" + "format:${{ needs.format.result }}" + "test:${{ needs.test.result }}" + "integration-winccoa:${{ needs.integration-winccoa.result }}" + ) + + echo "Job results:" + printf ' - %s\n' "${RESULTS[@]}" + + FAIL=0 + for r in "${RESULTS[@]}"; do + status="${r#*:}" + case "$status" in + success|skipped) + ;; + *) + echo "::error::Required job did not succeed: $r" + FAIL=1 + ;; + esac + done + + if [ "$FAIL" -ne 0 ]; then + exit 1 + fi diff --git a/.github/workflows/gitflow-validation.yml b/.github/workflows/gitflow-validation.yml index 4aa3f0a..1245050 100644 --- a/.github/workflows/gitflow-validation.yml +++ b/.github/workflows/gitflow-validation.yml @@ -117,6 +117,23 @@ jobs: core.notice('Reminder: delete the branch after merge (feature/bugfix/release/hotfix).'); } + required: + name: Git Flow Validation - Required + runs-on: ubuntu-latest + if: ${{ always() && github.event.action != 'closed' }} + needs: + - validate-gitflow + steps: + - name: Verify required jobs + shell: bash + run: | + set -euo pipefail + echo "validate-gitflow: ${{ needs.validate-gitflow.result }}" + if [ "${{ needs.validate-gitflow.result }}" != "success" ]; then + echo "::error::Git Flow Validation failed" + exit 1 + fi + remind-branch-deletion: name: Remind to delete merged branch runs-on: ubuntu-latest diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml index a009645..db4738d 100644 --- a/.github/workflows/pr-labels.yml +++ b/.github/workflows/pr-labels.yml @@ -21,3 +21,20 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler.yml + + required: + name: PR Labels - Required + runs-on: ubuntu-latest + if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository }} + needs: + - label + steps: + - name: Verify required jobs + shell: bash + run: | + set -euo pipefail + echo "label: ${{ needs.label.result }}" + if [ "${{ needs.label.result }}" != "success" ]; then + echo "::error::PR Labels failed" + exit 1 + fi diff --git a/.github/workflows/prerelease-reusable.yml b/.github/workflows/prerelease-reusable.yml index 455c7fa..acbd6a6 100644 --- a/.github/workflows/prerelease-reusable.yml +++ b/.github/workflows/prerelease-reusable.yml @@ -47,13 +47,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ inputs.target_branch != '' && inputs.target_branch || github.sha }} - name: Use Node.js 20.x - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20.x cache: "npm" @@ -92,7 +92,7 @@ jobs: run: vsce package - name: Upload VSIX artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ format('pre-release-{0}', github.sha) }} path: "*.vsix" diff --git a/.github/workflows/release-reusable.yml b/.github/workflows/release-reusable.yml index 2c5172b..0428d3d 100644 --- a/.github/workflows/release-reusable.yml +++ b/.github/workflows/release-reusable.yml @@ -77,7 +77,7 @@ jobs: needs: [versioning, precheck, tag] steps: - name: Use Node.js 20.x - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f04dc65..7ac08c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,14 @@ name: Release on: - pull_request: + workflow_run: + workflows: + - "CI/CD Pipeline" + - "Integration Tests - WinCC OA" + types: + - completed branches: - main - types: - - closed workflow_dispatch: inputs: {} @@ -19,18 +22,10 @@ permissions: jobs: release: - # Only run for merged release/hotfix PRs, or manual dispatch + # Only run if the triggering workflow succeeded or manual dispatch if: | github.event_name == 'workflow_dispatch' || - ( - github.event_name == 'pull_request' && - github.event.pull_request.merged == true && - ( - startsWith(github.head_ref, 'release/') || - startsWith(github.head_ref, 'hotfix/') || - contains(github.event.pull_request.labels.*.name, 'release') - ) - ) + github.event.workflow_run.conclusion == 'success' uses: ./.github/workflows/release-reusable.yml with: target_branch: main diff --git a/package-lock.json b/package-lock.json index 9557da7..a5b7d80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wincc-oa-tools-pack", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wincc-oa-tools-pack", - "version": "0.0.6", + "version": "0.0.7", "license": "MIT", "engines": { "vscode": "^1.60.0" diff --git a/package.json b/package.json index 8b74720..71071f4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "wincc-oa-tools-pack", "displayName": "WinCC OA Tools Pack", "publisher": "mPokornyETM", - "version": "0.0.6", + "version": "0.0.7", "engines": { "vscode": "^1.60.0" }, From 80b459dd1f733b352f51903092272cb32f5fe790 Mon Sep 17 00:00:00 2001 From: Martin Pokorny Date: Fri, 16 Jan 2026 11:08:16 +0100 Subject: [PATCH 05/10] chore: update CI/CD workflows to include main branch and remove unnecessary integration tests --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/gitflow.yml | 92 ++++++++++++++++++++++----- .github/workflows/release.yml | 1 - .github/workflows/required-checks.yml | 31 --------- 4 files changed, 76 insertions(+), 50 deletions(-) delete mode 100644 .github/workflows/required-checks.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b0cd03e..d49a8f3 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -2,7 +2,7 @@ name: CI/CD Pipeline on: push: - branches: [develop, release/**] + branches: [main, develop, release/**] pull_request: branches: [main, develop] diff --git a/.github/workflows/gitflow.yml b/.github/workflows/gitflow.yml index 4d62545..99fed56 100644 --- a/.github/workflows/gitflow.yml +++ b/.github/workflows/gitflow.yml @@ -1,4 +1,4 @@ -name: Gitflow Auto-Merge +name: Upmerge main to develop (PR) on: workflow_run: @@ -8,27 +8,85 @@ on: - completed branches: - main + workflow_dispatch: {} + +concurrency: + group: upmerge-main-to-develop + cancel-in-progress: true permissions: - contents: read + contents: write pull-requests: write jobs: - merge-back: - name: Gitflow + upmerge: + name: Create/Update upmerge PR runs-on: ubuntu-latest - # Only run if the release workflow succeeded - if: github.event.workflow_run.conclusion == 'success' - + steps: - - name: Run gitflow action - uses: Logerfo/gitflow-action@0.0.5 + - name: Checkout + uses: actions/checkout@v4 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - dev: develop - master: main - release: release - release-regex: '^release/(.*)' - label: gitflow - auto-merge: true - require-merge: false + fetch-depth: 0 + + - name: Configure git + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Create or update upmerge branch + PR + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_BRANCH: feature/upmerge-main-to-develop + BASE_BRANCH: develop + SOURCE_BRANCH: main + run: | + set -euo pipefail + + git fetch origin "${SOURCE_BRANCH}:${SOURCE_BRANCH}" "${BASE_BRANCH}:${BASE_BRANCH}" || true + git fetch origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}" "+refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH}" + + TITLE="chore(gitflow): upmerge ${SOURCE_BRANCH} -> ${BASE_BRANCH}" + BODY=$'Automated upmerge PR.\n\nSource: `main`\nTarget: `develop`\nBranch: `feature/upmerge-main-to-develop`\n\nIf there are merge conflicts:\n1) Checkout `feature/upmerge-main-to-develop` locally\n2) Run `git fetch origin` then `git merge origin/develop`\n3) Resolve conflicts, commit, and push' + + EXISTING_PR=$(gh pr list --state open --head "${HEAD_BRANCH}" --base "${BASE_BRANCH}" --json number --jq '.[0].number' || true) + + if [ -n "${EXISTING_PR}" ] && [ "${EXISTING_PR}" != "null" ]; then + echo "Found existing PR #${EXISTING_PR} for ${HEAD_BRANCH} -> ${BASE_BRANCH}" + git fetch origin "+refs/heads/${HEAD_BRANCH}:refs/remotes/origin/${HEAD_BRANCH}" || true + git checkout -B "${HEAD_BRANCH}" "origin/${HEAD_BRANCH}" + + # Bring in new commits from main + if ! git merge --no-edit "origin/${SOURCE_BRANCH}"; then + echo "::warning::Merge conflict while merging ${SOURCE_BRANCH} into ${HEAD_BRANCH}." + echo "::warning::Leaving branch as-is; please resolve conflicts locally." + git merge --abort || true + fi + else + echo "No existing PR found; creating ${HEAD_BRANCH} from ${SOURCE_BRANCH}" + git checkout -B "${HEAD_BRANCH}" "origin/${SOURCE_BRANCH}" + fi + + # Try to pre-resolve conflicts by merging develop into the head branch. + # This makes the eventual PR merge into develop much less likely to conflict. + DRAFT_FLAG="" + if ! git merge --no-edit "origin/${BASE_BRANCH}"; then + echo "::warning::Merge conflicts detected while merging ${BASE_BRANCH} into ${HEAD_BRANCH}." + echo "::warning::Creating/updating PR as draft; resolve conflicts on the branch." + git merge --abort || true + DRAFT_FLAG="--draft" + fi + + # Push the branch (force-with-lease to keep it aligned if recreated) + git push --force-with-lease origin "${HEAD_BRANCH}" + + if [ -n "${EXISTING_PR}" ] && [ "${EXISTING_PR}" != "null" ]; then + gh pr edit "${EXISTING_PR}" --title "${TITLE}" --body "${BODY}" >/dev/null + echo "Updated PR #${EXISTING_PR}" + else + PR_URL=$(gh pr create --head "${HEAD_BRANCH}" --base "${BASE_BRANCH}" --title "${TITLE}" --body "${BODY}" ${DRAFT_FLAG} --json url --jq .url) + echo "Created PR: ${PR_URL}" + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ac08c5..f7fc6dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,6 @@ on: workflow_run: workflows: - "CI/CD Pipeline" - - "Integration Tests - WinCC OA" types: - completed branches: diff --git a/.github/workflows/required-checks.yml b/.github/workflows/required-checks.yml deleted file mode 100644 index 4b6641f..0000000 --- a/.github/workflows/required-checks.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Required Checks - -on: - pull_request: - branches: - - main - - develop - types: - - opened - - reopened - - synchronize - - ready_for_review - - edited - -permissions: - actions: read - -jobs: - gate: - name: Gate - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - uses: winccoa-tools-pack/.github/.github/workflows/wait-for-workflows-reusable.yml@main - with: - sha: ${{ github.event.pull_request.head.sha }} - workflows: | - CI/CD Pipeline - PR Labels - Git Flow Validation - timeout_seconds: 1800 - poll_interval_seconds: 10 - secrets: inherit From 5d2eaaf1d858c252ddd7cbaeedec5d13d4e3c9b7 Mon Sep 17 00:00:00 2001 From: Martin Pokorny Date: Fri, 16 Jan 2026 11:48:08 +0100 Subject: [PATCH 06/10] chore: update release workflows to use dynamic target branch input --- .github/workflows/release-reusable.yml | 4 +++- .github/workflows/release.yml | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-reusable.yml b/.github/workflows/release-reusable.yml index 0428d3d..584b460 100644 --- a/.github/workflows/release-reusable.yml +++ b/.github/workflows/release-reusable.yml @@ -27,6 +27,7 @@ jobs: uses: winccoa-tools-pack/.github/.github/workflows/versioning-tags-changelog-reusable.yml@main with: mode: release + target_branch: ${{ inputs.target_branch }} create_tag: false push_tag: false secrets: inherit @@ -67,6 +68,7 @@ jobs: needs: [precheck] with: mode: release + target_branch: ${{ inputs.target_branch }} create_tag: true push_tag: true force_push_tag: false @@ -126,7 +128,7 @@ jobs: with: tag_name: ${{ needs.tag.outputs.tag }} name: ${{ format('Release {0}', needs.tag.outputs.tag) }} - target_commitish: ${{ github.sha }} + target_commitish: ${{ inputs.target_branch }} body: | ${{ needs.versioning.outputs.changelog }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7fc6dd..c693a9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,10 @@ jobs: # Only run if the triggering workflow succeeded or manual dispatch if: | github.event_name == 'workflow_dispatch' || - github.event.workflow_run.conclusion == 'success' + ( + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main' + ) uses: ./.github/workflows/release-reusable.yml with: target_branch: main From 01a0e4f7e6c3a770b0fce2c33754b2f7e219f3d6 Mon Sep 17 00:00:00 2001 From: Martin Pokorny <89339813+mPokornyETM@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:20:58 +0100 Subject: [PATCH 07/10] chore: add initial changelog file following Keep a Changelog format (#21) --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..11bddf3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# 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). + +## [Unreleased] diff --git a/package-lock.json b/package-lock.json index a5b7d80..9333333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wincc-oa-tools-pack", - "version": "0.0.7", + "version": "0.0.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wincc-oa-tools-pack", - "version": "0.0.7", + "version": "0.0.8", "license": "MIT", "engines": { "vscode": "^1.60.0" diff --git a/package.json b/package.json index 71071f4..c833df4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "wincc-oa-tools-pack", "displayName": "WinCC OA Tools Pack", "publisher": "mPokornyETM", - "version": "0.0.7", + "version": "0.0.8", "engines": { "vscode": "^1.60.0" }, From ea9d093b5d1b2655d21db05077ca05cebcac5233 Mon Sep 17 00:00:00 2001 From: Martin Pokorny Date: Fri, 16 Jan 2026 20:56:52 +0100 Subject: [PATCH 08/10] fix(gitflow): improve PR creation and fetching logic in upmerge workflow --- .github/workflows/gitflow.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gitflow.yml b/.github/workflows/gitflow.yml index 99fed56..9763fe4 100644 --- a/.github/workflows/gitflow.yml +++ b/.github/workflows/gitflow.yml @@ -46,13 +46,18 @@ jobs: run: | set -euo pipefail - git fetch origin "${SOURCE_BRANCH}:${SOURCE_BRANCH}" "${BASE_BRANCH}:${BASE_BRANCH}" || true + REPO="${GITHUB_REPOSITORY}" + OWNER="${REPO%%/*}" + + # Fetch remote refs without writing into checked-out local branches git fetch origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}" "+refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH}" TITLE="chore(gitflow): upmerge ${SOURCE_BRANCH} -> ${BASE_BRANCH}" BODY=$'Automated upmerge PR.\n\nSource: `main`\nTarget: `develop`\nBranch: `feature/upmerge-main-to-develop`\n\nIf there are merge conflicts:\n1) Checkout `feature/upmerge-main-to-develop` locally\n2) Run `git fetch origin` then `git merge origin/develop`\n3) Resolve conflicts, commit, and push' - EXISTING_PR=$(gh pr list --state open --head "${HEAD_BRANCH}" --base "${BASE_BRANCH}" --json number --jq '.[0].number' || true) + # Find an existing open PR for HEAD_BRANCH -> BASE_BRANCH. + # Use GitHub API to avoid relying on gh CLI JSON flags that vary by version. + EXISTING_PR=$(gh api "repos/${REPO}/pulls?state=open&head=${OWNER}:${HEAD_BRANCH}&base=${BASE_BRANCH}" --jq '.[0].number' 2>/dev/null || true) if [ -n "${EXISTING_PR}" ] && [ "${EXISTING_PR}" != "null" ]; then echo "Found existing PR #${EXISTING_PR} for ${HEAD_BRANCH} -> ${BASE_BRANCH}" @@ -87,6 +92,12 @@ jobs: gh pr edit "${EXISTING_PR}" --title "${TITLE}" --body "${BODY}" >/dev/null echo "Updated PR #${EXISTING_PR}" else - PR_URL=$(gh pr create --head "${HEAD_BRANCH}" --base "${BASE_BRANCH}" --title "${TITLE}" --body "${BODY}" ${DRAFT_FLAG} --json url --jq .url) - echo "Created PR: ${PR_URL}" + CREATE_OUT=$(gh pr create --head "${HEAD_BRANCH}" --base "${BASE_BRANCH}" --title "${TITLE}" --body "${BODY}" ${DRAFT_FLAG}) + echo "$CREATE_OUT" + PR_URL=$(echo "$CREATE_OUT" | grep -Eo 'https://github\.com/[^ ]+/pull/[0-9]+' | head -n 1 || true) + if [ -n "${PR_URL}" ]; then + echo "Created PR: ${PR_URL}" + else + echo "::warning::PR created but URL could not be parsed from output." + fi fi From 0f63e5f8207f7e5df099bc179b166faa0f7cce5e Mon Sep 17 00:00:00 2001 From: Martin Pokorny Date: Fri, 16 Jan 2026 21:02:30 +0100 Subject: [PATCH 09/10] feat(gitflow): add rule for merging main into develop in validation workflow --- .github/workflows/gitflow-validation.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/gitflow-validation.yml b/.github/workflows/gitflow-validation.yml index 1245050..06bc418 100644 --- a/.github/workflows/gitflow-validation.yml +++ b/.github/workflows/gitflow-validation.yml @@ -35,6 +35,8 @@ jobs: const baseRef = pr.base.ref; // e.g. develop const rules = [ + // main to develop means merging main back into develop (git flow) + { type: 'main', prefixes: ['main'], expectedBase: 'develop' }, { type: 'feature', prefixes: ['feature/'], expectedBase: 'develop' }, { type: 'bugfix', prefixes: ['bugfix/'], expectedBase: 'develop' }, { type: 'chore', prefixes: ['chore/'], expectedBase: 'develop', botOnly: true }, From 03d124938fbaf9b4fe78c2cfa47660d3a190875d Mon Sep 17 00:00:00 2001 From: Martin Pokorny Date: Fri, 16 Jan 2026 21:05:27 +0100 Subject: [PATCH 10/10] fix(gitflow): update trigger for upmerge workflow to respond to push events on main branch --- .github/workflows/gitflow.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gitflow.yml b/.github/workflows/gitflow.yml index 9763fe4..c620905 100644 --- a/.github/workflows/gitflow.yml +++ b/.github/workflows/gitflow.yml @@ -1,13 +1,8 @@ name: Upmerge main to develop (PR) on: - workflow_run: - workflows: - - "Release" - types: - - completed - branches: - - main + push: + branches: [main] workflow_dispatch: {} concurrency: