Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0da0432af
|
||
|
|
dd527c1443
|
||
|
|
d586e6bb7d
|
||
|
|
7edac8b639
|
No files matched your search
@@ -10,3 +10,4 @@ COM_ERMNVLDMR_STATIC_S3_REGION="encrypted:BAj3iuo7tWWNR2V4yQ+LHnFCB4JcZp7TTfUtvv
|
||||
COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL="encrypted:BNkQrgsrxtz544vuJbgwWUh5cPjHG5wbmHNcxTUWrzbFJJsdncaAitviCvIrS7Q41w+FiWkssv3D05Tu8Inn2P62E6G3PU4gNxO2oLdmGEdrzUqB/xvxOvLvos42j54QOZf7lOqMPKH6SL2/rheCAo10gSCicCqkfh4xsCAbYpyL"
|
||||
COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID="encrypted:BCsp8p4bp3cB+9grxHo634isRUaFGU3MOc1lr8ClSFRYLjqjqukyrbJCTZWV2Xdo8iQ33EylmbzUltbwzTWMQcjJSvIskesVLyV93q60o/Mu9L9jg/apwHuBgHv4zVuHstJ0zkHWgapbNTIlWD3RCm0ujeX/H1gBH3U="
|
||||
COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY="encrypted:BM7aPliOyiPrqFPZMfqpn534Nu+Y0Nbh/YRoxv2KY6XAi2dNO0LSEf97DqSUQYdsnu9iibZmVNktF2K/D6mtHr9+gumF6KsmaLH65KE7c9ly7NdFQHL9I26wxRq1Sr3Zgrfs954J6oURSlBRKqJFdcTsBltuuupGuqJHyLS9+DE0jD+bl0Qg0lA="
|
||||
SOPS_AGE_KEY="encrypted:BOJ7q79Tws4DMxpFuSymgv//IC4f+j713Bd7mNlUCEaUYVSfJMayMVPzks0FcsYo12XvlbH6gQ+tCf3t9yj/LcKKJzl3wtxlSVjvCRr0hasEBohcoxkLnFZ73DFQGzWcVC5Jf5IMK7qdYdNVzW0dVY/sn/NlTgl+A3YB6/08XYPhljl9993ky76zQqWDJQ66bKJ7YxWvst2TUNoT0lJEL0M4pg9ztRsuGqNZ"
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
name: github-fix
|
||||
description: Diagnose CI failures and review feedback on GitHub Pull Requests or Issues, repair code, verify quality checks, and commit changes.
|
||||
---
|
||||
|
||||
# GitHub Fix Skill
|
||||
|
||||
Use this skill when a user submits a `/fix` command on a GitHub Pull Request or Issue.
|
||||
|
||||
## Overview
|
||||
|
||||
The `/fix` command resolves build failures, test errors, and reviewer feedback. This skill enforces unified diagnosis, clean code repair, strict verification, and transparent progress reporting.
|
||||
|
||||
```text
|
||||
/fix [optional instructions]
|
||||
│
|
||||
├── 1. Unified Diagnosis (Fetch CI logs, inspect diff, parse comments)
|
||||
├── 2. Target Code Repair (Fix build errors and review feedback in one pass)
|
||||
├── 3. Verification (Run typecheck, lint, and tests)
|
||||
├── 4. Git Operations (Commit and push to the active branch)
|
||||
└── 5. Thread Summary (Post itemized comment with mandatory disclosure)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Operational Workflow
|
||||
|
||||
### Step 1: Unified Diagnosis
|
||||
|
||||
Collect all diagnostic context before making code edits.
|
||||
|
||||
#### Case A: Pull Request Context
|
||||
1. Identify the current PR number and branch:
|
||||
```bash
|
||||
gh pr view --json number,headRefName,baseRefName
|
||||
```
|
||||
2. Fetch failed CI workflow logs:
|
||||
```bash
|
||||
gh run view --log-failed
|
||||
```
|
||||
3. Inspect current changes in the PR:
|
||||
```bash
|
||||
gh pr diff
|
||||
```
|
||||
4. Read all review comments and discussion threads:
|
||||
```bash
|
||||
gh pr view --comments
|
||||
```
|
||||
5. Check inline review comments on specific files:
|
||||
```bash
|
||||
gh api repos/{owner}/{repo}/pulls/{pull_number}/comments
|
||||
```
|
||||
|
||||
#### Case B: Issue Context
|
||||
1. Read the issue description and thread comments:
|
||||
```bash
|
||||
gh issue view <issue-number> --comments
|
||||
```
|
||||
2. Extract error stack traces, reproduction steps, and user instructions.
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Target Code Repair
|
||||
|
||||
Apply all necessary corrections in a single pass.
|
||||
|
||||
1. Address all failures from CI logs (TypeScript errors, linter violations, test failures).
|
||||
2. Apply requested changes from code review comments.
|
||||
3. Incorporate any optional user instructions provided with `/fix`.
|
||||
4. Obey all repository conventions defined in `GEMINI.md`:
|
||||
- **Strict TypeScript**: Do not use `any`, `as Type` assertions, or non-null `!` assertions. Use `castRef` from `@ermnvldmr/stl` when casting DOM references.
|
||||
- **Type Imports**: Use `import type` for type declarations.
|
||||
- **Interfaces**: Define object contracts with `interface`, not `type` aliases.
|
||||
- **TSDoc**: Add descriptive JSDoc comments to all exported symbols and interfaces.
|
||||
- **Styling**: Use Tailwind CSS v4 and `cva`. Merge class names with `cn` from `@ermnvldmr/stl`.
|
||||
- **UI Primitives**: Use React Aria hooks and Radix UI primitives.
|
||||
- **Import Boundaries**: Use relative paths for files within the same workspace package.
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Verification
|
||||
|
||||
Execute the complete verification sequence. All checks must pass with zero warnings or errors before proceeding.
|
||||
|
||||
```bash
|
||||
# 1. Verify TypeScript types
|
||||
pnpm typecheck
|
||||
|
||||
# 2. Verify ESLint rules
|
||||
pnpm lint
|
||||
|
||||
# 3. Run unit and component tests
|
||||
pnpm test
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If any check fails, resolve the failure immediately and rerun the verification sequence.
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Git Operations
|
||||
|
||||
Stage, commit, and push the resolved changes to the active branch.
|
||||
|
||||
1. Stage modified and created files:
|
||||
```bash
|
||||
git add <modified-files>
|
||||
```
|
||||
2. Commit with standard message:
|
||||
```bash
|
||||
git commit -m "fix: apply review adjustments and CI fixes"
|
||||
```
|
||||
3. Push changes to the remote branch:
|
||||
```bash
|
||||
git push origin HEAD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Post Summary Comment
|
||||
|
||||
Post an itemized summary comment to the GitHub PR or Issue thread.
|
||||
|
||||
1. Format the comment with the template below.
|
||||
2. Submit the comment via GitHub CLI:
|
||||
- For a Pull Request:
|
||||
```bash
|
||||
gh pr comment <pr-number> --body "<comment-body>"
|
||||
```
|
||||
- For an Issue:
|
||||
```bash
|
||||
gh issue comment <issue-number> --body "<comment-body>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GitHub Comment Template
|
||||
|
||||
```markdown
|
||||
### Automated Fix Summary
|
||||
|
||||
I addressed the reported issues and review feedback.
|
||||
|
||||
#### Root Causes Identified
|
||||
- **CI / Test Failure**: `<Brief description of the failure>`
|
||||
- **Review Feedback**: `<Brief description of reviewer comments>`
|
||||
|
||||
#### Changes Applied
|
||||
- `packages/<pkg>/src/...`: Fixed `<specific problem>`
|
||||
- `services/<svc>/src/...`: Updated `<specific problem>`
|
||||
|
||||
#### Verification Results
|
||||
- `pnpm typecheck` passed (0 errors).
|
||||
- `pnpm lint` passed (0 warnings).
|
||||
- `pnpm test` passed (all tests clean).
|
||||
|
||||
Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before completing the task, verify every item in this checklist:
|
||||
|
||||
- [ ] Failed CI logs inspected with `gh run view --log-failed`.
|
||||
- [ ] PR diff and review comments retrieved and analyzed.
|
||||
- [ ] Code repairs satisfy all requirements without introducing regressions.
|
||||
- [ ] No `any`, `as Type`, or `!` operators introduced.
|
||||
- [ ] `pnpm typecheck` completed with 0 errors.
|
||||
- [ ] `pnpm lint` completed with 0 warnings.
|
||||
- [ ] `pnpm test` completed with all tests passing.
|
||||
- [ ] Changes committed and pushed to the active branch.
|
||||
- [ ] Summary comment posted to the PR or Issue.
|
||||
- [ ] Mandatory disclosure line included at the end of the comment.
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
name: github-implement
|
||||
description: Implement code changes from an approved issue plan, run monorepo validation, and create a pull request.
|
||||
---
|
||||
|
||||
# GitHub Implement Skill
|
||||
|
||||
Use this skill when an issue receives the `/implement` command.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill guides the implementation of approved design plans. It enforces repository rules, executes automated validation, and prepares pull requests for the `next` branch.
|
||||
|
||||
## Procedural Workflow
|
||||
|
||||
### Phase 1: Context Ingestion
|
||||
|
||||
1. Read the complete issue description and title.
|
||||
2. Locate the approved implementation plan in the issue comments.
|
||||
3. Parse additional instructions from the `/implement` comment.
|
||||
4. If requirements conflict or miss critical details, stop and request clarification.
|
||||
|
||||
### Phase 2: Code Implementation
|
||||
|
||||
Apply all modifications according to `GEMINI.md` conventions:
|
||||
|
||||
1. **TypeScript Constraints**:
|
||||
- Enable strict typing for all new code.
|
||||
- Do not use the `any` type.
|
||||
- Do not use type assertions (`as Type`).
|
||||
- Do not use the non-null assertion operator (`!`).
|
||||
- Use explicit type guards or helper functions such as `castRef`.
|
||||
- Use `import type` for type-only imports.
|
||||
- Define object structures with `interface`, not `type` aliases.
|
||||
|
||||
2. **Documentation Rules**:
|
||||
- Add JSDoc comments to all exported functions, interfaces, and types.
|
||||
- Include clear description text in every JSDoc block.
|
||||
|
||||
3. **Component Architecture**:
|
||||
- Place components in dedicated directories under `packages/ui/src/components/<ComponentName>/`.
|
||||
- Provide `<ComponentName>.tsx` for component logic.
|
||||
- Provide `<ComponentName>.stories.tsx` for Storybook stories.
|
||||
- Provide `<ComponentName>.test.tsx` for Vitest tests.
|
||||
- Use `cva` for component variant management.
|
||||
- Merge CSS utility classes with `cn(...)` from `@ermnvldmr/stl`.
|
||||
- Use React Aria hooks and Radix UI primitives for accessible controls.
|
||||
|
||||
4. **Monorepo Imports and State**:
|
||||
- Use relative paths within the same package.
|
||||
- Do not use self-referential package names.
|
||||
- Use standard React state primitives. Do not add unapproved state managers.
|
||||
|
||||
5. **Protected Boundaries**:
|
||||
- Do not modify `.env.keys`, `pnpm-lock.yaml`, or `LICENSE`.
|
||||
- Do not edit content files in `services/docs/content/` or `services/www/content/` unless explicitly instructed.
|
||||
|
||||
### Phase 3: Monorepo Verification & Self-Correction
|
||||
|
||||
Run validation commands in strict sequence:
|
||||
|
||||
1. Run `pnpm typecheck`. Ensure zero compiler errors.
|
||||
2. Run `pnpm lint`. Ensure zero warnings and zero errors.
|
||||
3. Run `pnpm test`. Ensure all unit and component tests pass.
|
||||
4. Run `pnpm build`. Ensure clean compilation across all packages.
|
||||
|
||||
**Iterative Self-Correction Loop**:
|
||||
- If any check fails, inspect the diagnostic output.
|
||||
- Identify the root cause.
|
||||
- Apply targeted fixes adhering to all code rules.
|
||||
- Re-run the verification sequence from step 1.
|
||||
- Repeat until all four checks pass completely.
|
||||
|
||||
### Phase 4: Commit & Pull Request Preparation
|
||||
|
||||
1. **Commit Message**:
|
||||
- Format commits according to Conventional Commits: `<type>: implement issue #<number> (<title>)`.
|
||||
- Example: `feat: implement issue #42 (add theme toggle)`.
|
||||
|
||||
2. **Pull Request Target**:
|
||||
- Set base branch to `next`.
|
||||
|
||||
3. **Pull Request Body**:
|
||||
- Link the issue: `Resolves #<number>`.
|
||||
- Provide a concise summary of changes.
|
||||
- Detail the implementation walkthrough.
|
||||
- List the executed verification checks.
|
||||
- Append the mandatory attribution line.
|
||||
|
||||
4. **Mandatory Attribution**:
|
||||
- Conclude all PR bodies and issue comments with:
|
||||
`Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.`
|
||||
|
||||
## Pull Request Template
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
Resolves #<number>
|
||||
|
||||
<Brief explanation of the implemented feature or fix>
|
||||
|
||||
## Walkthrough of Changes
|
||||
- `<package-or-path>`: <Description of specific change>
|
||||
- `<package-or-path>`: <Description of specific change>
|
||||
|
||||
## Verification Results
|
||||
- [x] `pnpm typecheck` passed (0 errors)
|
||||
- [x] `pnpm lint` passed (0 warnings)
|
||||
- [x] `pnpm test` passed (all tests green)
|
||||
- [x] `pnpm build` passed (clean build)
|
||||
|
||||
Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.
|
||||
```
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Issue context, approved plan, and user instructions are satisfied.
|
||||
- [ ] TypeScript strict mode rules are respected (`no any`, `no as`, `no !`).
|
||||
- [ ] Exported symbols include JSDoc comments.
|
||||
- [ ] UI components reside in dedicated folders with tests and stories.
|
||||
- [ ] Monorepo boundaries and relative imports are respected.
|
||||
- [ ] All four verification checks passed cleanly.
|
||||
- [ ] Target branch is set to `next`.
|
||||
- [ ] PR description includes `Resolves #<number>`.
|
||||
- [ ] Mandatory attribution line is present.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
name: github-plan
|
||||
description: Analyze an issue thread, iterate on feedback, and produce or update an implementation plan for ermnvldmr.com.
|
||||
---
|
||||
|
||||
# GitHub Issue Planning Skill (ASD-STE100 Standard)
|
||||
|
||||
## Overview
|
||||
|
||||
Use this skill when an issue receives the `/plan [optional instructions]` command.
|
||||
|
||||
This skill creates or updates an implementation plan before code modification starts. All outputs must follow ASD-STE100 language constraints and repository standards.
|
||||
|
||||
---
|
||||
|
||||
## ASD-STE100 Writing Rules
|
||||
|
||||
Follow these rules for all generated plans and documentation:
|
||||
|
||||
| Rule | Limit | Approved Example | Non-Approved Example |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Procedural Sentences** | Max **20 words** | "Run `pnpm test`. Verify that all unit tests pass." | "You should run the pnpm test command to verify that all the unit tests pass properly without errors." |
|
||||
| **Descriptive Sentences** | Max **25 words** | "The `Button` component uses `cva` to manage style variants." | "The Button component is implemented using the cva library because it helps us manage complex style variants across different themes easily." |
|
||||
| **Voice** | **Active voice** | "The function validates the user input." | "The user input is validated by the function." |
|
||||
| **Mood and Tense** | **Present tense** and **Imperative mood** | "Click **Submit**. The form transmits the data." | "You will click submit and the form will be transmitting the data." |
|
||||
| **Noun Clusters** | Max **3 nouns** | "Theme configuration file" | "Theme variant style configuration file" |
|
||||
|
||||
### Controlled Vocabulary Guidelines
|
||||
- Do not use conversational filler (for example: "basically", "let's dive in", "simply", "feel free to").
|
||||
- Use approved verbs: use *use* (not *utilize*), *stop* (not *terminate*), *start* (not *initiate*), *show* (not *exhibit*).
|
||||
- Use clear and literal technical terms.
|
||||
|
||||
---
|
||||
|
||||
## Planning Responsibilities
|
||||
|
||||
### 1. Ingest Full Issue Context
|
||||
- Read the issue title, issue description, and all subsequent comments.
|
||||
- Identify user requirements, technical constraints, and reported symptoms.
|
||||
- Inspect relevant source files in the repository before proposing changes.
|
||||
- Read `GEMINI.md` to confirm current project conventions.
|
||||
|
||||
### 2. Multi-Turn Plan Iteration
|
||||
- Check if an earlier implementation plan exists in the issue thread.
|
||||
- If an earlier plan exists:
|
||||
- Do not create a plan from zero.
|
||||
- Retain approved architectural decisions from previous turns.
|
||||
- Apply the requested modifications or additions from the latest comment.
|
||||
- Summarize the specific adjustments made to the previous plan.
|
||||
- If no earlier plan exists:
|
||||
- Create a new baseline implementation plan from the issue requirements.
|
||||
|
||||
### 3. Enforce Repository Architecture (`GEMINI.md`)
|
||||
Every proposed plan must comply with repository architecture:
|
||||
- **Build System**: Use Rsbuild and Rspack. Do not propose Vite, Webpack, or Next.js.
|
||||
- **Strict TypeScript**:
|
||||
- Do not use `any`.
|
||||
- Do not use type assertions (`as Type`). Use type guards or `castRef` from `@ermnvldmr/stl`.
|
||||
- Do not use non-null assertions (`!`).
|
||||
- Use `import type` for type-only imports.
|
||||
- Define object shapes with `interface`, not `type` aliases.
|
||||
- Require JSDoc comments on all exported symbols.
|
||||
- **Component Architecture** (`packages/ui`):
|
||||
- Place components in `packages/ui/src/components/<ComponentName>/`.
|
||||
- Provide `<ComponentName>.tsx`, `<ComponentName>.stories.tsx`, and `<ComponentName>.test.tsx`.
|
||||
- Use `cva` for variant definitions.
|
||||
- Merge class names with `cn(...)` from `@ermnvldmr/stl`.
|
||||
- **Accessibility & State**:
|
||||
- Use React Aria hooks (`useButton`, `useFocusRing`, `useHover`) and Radix UI primitives.
|
||||
- Use standard React state (`useState`, `useReducer`, `useContext`) and `react-stately`.
|
||||
- Do not introduce external state management libraries.
|
||||
- **Monorepo Import Rules**:
|
||||
- Use relative import paths within the same package.
|
||||
- Do not import `@ermnvldmr/<package>` inside `<package>` itself.
|
||||
- **Protected Files**:
|
||||
- Do not modify `.env.keys`, `pnpm-lock.yaml`, `LICENSE`, `services/docs/content/`, or `services/www/content/`.
|
||||
|
||||
---
|
||||
|
||||
## Output Plan Structure
|
||||
|
||||
Format the generated plan with the following markdown template:
|
||||
|
||||
```markdown
|
||||
### Goal
|
||||
[Describe the objective in 1-2 sentences. Keep each sentence under 25 words.]
|
||||
|
||||
### Architecture & Technical Decisions
|
||||
- [Decision 1: Explain pattern, primitive, or component structure.]
|
||||
- [Decision 2: Explain typing or API contracts.]
|
||||
- [Decision 3: Explain state management or accessibility integration.]
|
||||
|
||||
### File Changes
|
||||
- `[NEW]` `packages/ui/src/components/NewComponent/NewComponent.tsx` - [Purpose]
|
||||
- `[NEW]` `packages/ui/src/components/NewComponent/NewComponent.stories.tsx` - [Purpose]
|
||||
- `[NEW]` `packages/ui/src/components/NewComponent/NewComponent.test.tsx` - [Purpose]
|
||||
- `[MODIFY]` `packages/ui/src/index.ts` - [Export new component]
|
||||
|
||||
### Verification Plan
|
||||
1. `pnpm typecheck` - Verify zero TypeScript compiler errors.
|
||||
2. `pnpm lint` - Verify zero ESLint warnings and errors.
|
||||
3. `pnpm test` - Run unit and integration tests.
|
||||
4. `pnpm build` - Run full workspace build.
|
||||
|
||||
### Open Questions
|
||||
- [List open questions or ambiguities. If no open questions exist, write "None.".]
|
||||
|
||||
Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Traps and Solutions
|
||||
|
||||
| Anti-Pattern | Correct Action |
|
||||
| :--- | :--- |
|
||||
| Regenerating a new plan after user feedback | Update the existing plan and highlight modified sections. |
|
||||
| Using `as Type` assertions in the proposed code | Propose explicit type guards or `castRef` helpers. |
|
||||
| Missing Storybook or Vitest files for UI components | Include `.stories.tsx` and `.test.tsx` in the file plan. |
|
||||
| Proposing external state libraries | Use React 19 primitives and `react-stately`. |
|
||||
| Omitting the attribution line | Append the mandatory disclosure line at the end. |
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before posting the plan response, verify all items:
|
||||
|
||||
- [ ] The issue thread context and all user comments are analyzed.
|
||||
- [ ] Existing plans are updated rather than replaced from scratch.
|
||||
- [ ] Proposed code satisfies all `GEMINI.md` constraints (`strict: true`, no `any`, no `as`, no `!`).
|
||||
- [ ] All UI components include unit tests and Storybook stories.
|
||||
- [ ] All sentences follow ASD-STE100 limits (<=20 words for instructions, <=25 words for descriptions).
|
||||
- [ ] Active voice is used throughout the plan.
|
||||
- [ ] The output concludes with: `Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.`
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
name: gemini - manual
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: agent-issue-${{ github.event.issue.number || github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
agent-dispatch:
|
||||
name: Agent / Dispatch
|
||||
if: >
|
||||
(github.event.comment.author_association == 'OWNER' ||
|
||||
github.event.comment.author_association == 'MEMBER' ||
|
||||
github.event.comment.author_association == 'COLLABORATOR') &&
|
||||
(contains(github.event.comment.body, '/plan') ||
|
||||
contains(github.event.comment.body, '/implement') ||
|
||||
contains(github.event.comment.body, '/fix'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Verify Collaborator Authorization
|
||||
id: auth-check
|
||||
run: |
|
||||
AUTHOR_ASSOC="${{ github.event.comment.author_association }}"
|
||||
echo "Author association: $AUTHOR_ASSOC"
|
||||
if [[ "$AUTHOR_ASSOC" != "OWNER" && "$AUTHOR_ASSOC" != "MEMBER" && "$AUTHOR_ASSOC" != "COLLABORATOR" ]]; then
|
||||
echo "::error::Unauthorized trigger by user with association '$AUTHOR_ASSOC'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Acknowledge Command (Add Eyes Reaction)
|
||||
if: success()
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
COMMENT_ID: ${{ github.event.comment.id }}
|
||||
run: |
|
||||
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}/reactions" -f content=eyes || true
|
||||
|
||||
- name: Checkout Codebase
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
|
||||
- name: Setup Node.js with pnpm cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies & Build Workspace Packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build:deps
|
||||
|
||||
- name: Restore Agent Conversation Context
|
||||
uses: actions/cache@v4
|
||||
id: agent-context-cache
|
||||
with:
|
||||
path: |
|
||||
~/.gemini/antigravity-cli/conversations
|
||||
~/.gemini/antigravity-cli/brain
|
||||
key: agent-context-issue-${{ github.event.issue.number }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
agent-context-issue-${{ github.event.issue.number }}-
|
||||
|
||||
- name: Setup Antigravity CLI & Credentials
|
||||
env:
|
||||
AGY_AUTH_TOKEN: ${{ secrets.AGY_AUTH_TOKEN }}
|
||||
run: |
|
||||
# 1. Install Antigravity CLI (agy) if not present
|
||||
if ! command -v agy &> /dev/null; then
|
||||
echo "Installing Antigravity CLI (agy)..."
|
||||
curl -fsSL https://antigravity.google/install.sh | bash || true
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
echo "$HOME/.gemini/antigravity-cli/bin" >> $GITHUB_PATH
|
||||
export PATH="$HOME/.local/bin:$HOME/.gemini/antigravity-cli/bin:$PATH"
|
||||
fi
|
||||
|
||||
# 2. Inject OAuth Token
|
||||
mkdir -p ~/.gemini/antigravity-cli
|
||||
if [ -n "$AGY_AUTH_TOKEN" ]; then
|
||||
if echo "$AGY_AUTH_TOKEN" | base64 --decode >/dev/null 2>&1; then
|
||||
echo "$AGY_AUTH_TOKEN" | base64 --decode > ~/.gemini/antigravity-cli/antigravity-oauth-token
|
||||
else
|
||||
echo "$AGY_AUTH_TOKEN" > ~/.gemini/antigravity-cli/antigravity-oauth-token
|
||||
fi
|
||||
chmod 600 ~/.gemini/antigravity-cli/antigravity-oauth-token
|
||||
else
|
||||
echo "::warning::AGY_AUTH_TOKEN secret is not set."
|
||||
fi
|
||||
|
||||
- name: Configure Git Bot Identity
|
||||
run: |
|
||||
git config --global user.name "AI Assistant Bot"
|
||||
git config --global user.email "bot@users.noreply.github.com"
|
||||
|
||||
# --- COMMAND: /plan ---
|
||||
- name: Handle /plan (Issue Planning)
|
||||
if: contains(github.event.comment.body, '/plan') && github.event.issue.pull_request == null
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
echo "Generating plan for Issue #${ISSUE_NUMBER}..."
|
||||
ISSUE_DATA=$(gh issue view "$ISSUE_NUMBER" --comments --json title,body,comments)
|
||||
|
||||
CONTINUE_FLAG=""
|
||||
if [ -d "$HOME/.gemini/antigravity-cli/conversations" ] && [ "$(ls -A $HOME/.gemini/antigravity-cli/conversations 2>/dev/null)" ]; then
|
||||
CONTINUE_FLAG="--continue"
|
||||
fi
|
||||
|
||||
PROMPT="You are operating via GitHub Actions on Issue #${ISSUE_NUMBER}.\n\nIssue Context:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nPlease generate/update a detailed technical implementation plan adhering to project GEMINI.md guidelines."
|
||||
|
||||
PLAN_OUTPUT=$(agy $CONTINUE_FLAG --mode plan -p "$PROMPT" || echo "Agent encountered an error generating the plan.")
|
||||
|
||||
DISCLOSURE="\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*"
|
||||
gh issue comment "$ISSUE_NUMBER" --body "${PLAN_OUTPUT}${DISCLOSURE}"
|
||||
|
||||
# --- COMMAND: /implement ---
|
||||
- name: Handle /implement (Issue Implementation & PR)
|
||||
if: contains(github.event.comment.body, '/implement') && github.event.issue.pull_request == null
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
BRANCH_NAME="agent/issue-${ISSUE_NUMBER}"
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
ISSUE_DATA=$(gh issue view "$ISSUE_NUMBER" --comments --json title,body,comments)
|
||||
|
||||
CONTINUE_FLAG=""
|
||||
if [ -d "$HOME/.gemini/antigravity-cli/conversations" ] && [ "$(ls -A $HOME/.gemini/antigravity-cli/conversations 2>/dev/null)" ]; then
|
||||
CONTINUE_FLAG="--continue"
|
||||
fi
|
||||
|
||||
PROMPT="You are operating via GitHub Actions to implement Issue #${ISSUE_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nImplement the changes cleanly in the repository according to GEMINI.md rules."
|
||||
agy $CONTINUE_FLAG --dangerously-skip-permissions -p "$PROMPT"
|
||||
|
||||
echo "Running repository verification gates..."
|
||||
pnpm typecheck
|
||||
pnpm lint
|
||||
pnpm test
|
||||
|
||||
ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --json title -q .title)
|
||||
git add -A
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes produced by agent."
|
||||
gh issue comment "$ISSUE_NUMBER" --body "No changes were produced for implementation.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "feat: implement issue #${ISSUE_NUMBER} (${ISSUE_TITLE})"
|
||||
git push -u origin "$BRANCH_NAME" --force
|
||||
|
||||
PR_BODY="Resolves #${ISSUE_NUMBER}\n\nAutomated implementation generated via \`/implement\`.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*"
|
||||
gh pr create \
|
||||
--title "feat: ${ISSUE_TITLE}" \
|
||||
--body "$PR_BODY" \
|
||||
--base next \
|
||||
--head "$BRANCH_NAME"
|
||||
|
||||
# --- COMMAND: /fix ---
|
||||
- name: Handle /fix (PR Review Adjustments & CI Failure Debugging)
|
||||
if: contains(github.event.comment.body, '/fix')
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
ISSUE_OR_PR_NUMBER: ${{ github.event.issue.number }}
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
IS_PR="${{ github.event.issue.pull_request != null }}"
|
||||
if [ "$IS_PR" = "true" ]; then
|
||||
IS_CROSS_REPO=$(gh pr view "$ISSUE_OR_PR_NUMBER" --json isCrossRepository -q .isCrossRepository)
|
||||
if [ "$IS_CROSS_REPO" = "true" ]; then
|
||||
echo "::error::Cross-repository fork PRs cannot be automatically processed in privileged context."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh pr checkout "$ISSUE_OR_PR_NUMBER"
|
||||
BRANCH_NAME=$(git branch --show-current)
|
||||
PR_DIFF=$(gh pr diff "$ISSUE_OR_PR_NUMBER" || true)
|
||||
|
||||
FAILED_RUN_ID=$(gh run list --branch "$BRANCH_NAME" --status failure --limit 1 --json databaseId -q '.[0].databaseId' || true)
|
||||
CI_LOGS=""
|
||||
if [ -n "$FAILED_RUN_ID" ] && [ "$FAILED_RUN_ID" != "null" ]; then
|
||||
echo "Found failed CI run: #$FAILED_RUN_ID. Fetching failure logs..."
|
||||
CI_LOGS=$(gh run view "$FAILED_RUN_ID" --log-failed | tail -n 200 || true)
|
||||
fi
|
||||
|
||||
CONTINUE_FLAG=""
|
||||
if [ -d "$HOME/.gemini/antigravity-cli/conversations" ] && [ "$(ls -A $HOME/.gemini/antigravity-cli/conversations 2>/dev/null)" ]; then
|
||||
CONTINUE_FLAG="--continue"
|
||||
fi
|
||||
|
||||
PROMPT="You are operating via GitHub Actions to fix/adjust PR #${ISSUE_OR_PR_NUMBER} on branch ${BRANCH_NAME}.\n\nFeedback / Comment:\n${COMMENT_BODY}\n\nPR Diff:\n${PR_DIFF}\n\nFailed CI Logs (if any):\n${CI_LOGS}\n\nApply the necessary fixes, making sure all lint, typecheck, and test checks pass."
|
||||
agy $CONTINUE_FLAG --dangerously-skip-permissions -p "$PROMPT"
|
||||
|
||||
echo "Running repository verification gates..."
|
||||
pnpm typecheck
|
||||
pnpm lint
|
||||
pnpm test
|
||||
|
||||
git add -A
|
||||
if git diff --staged --quiet; then
|
||||
echo "No additional changes were needed."
|
||||
gh pr comment "$ISSUE_OR_PR_NUMBER" --body "Analyzed the request; no additional changes were needed.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "fix: apply review adjustments and CI fixes"
|
||||
git push origin "$BRANCH_NAME"
|
||||
|
||||
gh pr comment "$ISSUE_OR_PR_NUMBER" --body "Applied adjustments and verified build/test suite.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*"
|
||||
else
|
||||
echo "Handling /fix on Issue #${ISSUE_OR_PR_NUMBER}..."
|
||||
BRANCH_NAME="agent/fix-issue-${ISSUE_OR_PR_NUMBER}"
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
ISSUE_DATA=$(gh issue view "$ISSUE_OR_PR_NUMBER" --comments --json title,body,comments)
|
||||
PROMPT="Fix issue #${ISSUE_OR_PR_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nInstruction:\n${COMMENT_BODY}"
|
||||
agy --dangerously-skip-permissions -p "$PROMPT"
|
||||
|
||||
pnpm typecheck
|
||||
pnpm lint
|
||||
pnpm test
|
||||
|
||||
git add -A
|
||||
git commit -m "fix: resolve issue #${ISSUE_OR_PR_NUMBER}"
|
||||
git push -u origin "$BRANCH_NAME" --force
|
||||
|
||||
gh pr create \
|
||||
--title "fix: resolve issue #${ISSUE_OR_PR_NUMBER}" \
|
||||
--body "Resolves #${ISSUE_OR_PR_NUMBER}\n\nFix applied via \`/fix\`.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" \
|
||||
--base next \
|
||||
--head "$BRANCH_NAME"
|
||||
fi
|
||||
|
||||
- name: Complete Command (Add Rocket Reaction)
|
||||
if: success()
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
COMMENT_ID: ${{ github.event.comment.id }}
|
||||
run: |
|
||||
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}/reactions" -f content=rocket || true
|
||||
|
||||
- name: Report Failure on Comment
|
||||
if: failure()
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: |
|
||||
gh issue comment "$ISSUE_NUMBER" --body "⚠️ Workflow failed during execution. View run logs: [Action Run #${{ github.run_id }}](${RUN_URL})\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" || true
|
||||
@@ -1,66 +0,0 @@
|
||||
name: services/www - next/checkout-release-branch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
increment_major:
|
||||
description: 'Force major version increment (e.g. v2.0 instead of v1.1)'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
workflow-release-branch:
|
||||
name: Workflow - Create or Update Release Branch
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine Mode and Branch Name
|
||||
id: plan
|
||||
run: |
|
||||
REF="${{ github.ref_name }}"
|
||||
if [[ "$REF" =~ ^releases/www/v[0-9]+\.[0-9]+$ ]]; then
|
||||
# Dispatched against an existing release branch: update it.
|
||||
echo "mode=update" >> "$GITHUB_OUTPUT"
|
||||
echo "branch_name=$REF" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Dispatched against next (or any other ref): create a new release branch.
|
||||
BRANCH_NAME=$(bash ci/scripts/semantic-release.sh checkout services/www "${{ inputs.increment_major }}")
|
||||
echo "mode=create" >> "$GITHUB_OUTPUT"
|
||||
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create and Push Branch
|
||||
if: steps.plan.outputs.mode == 'create'
|
||||
run: |
|
||||
git checkout -b ${{ steps.plan.outputs.branch_name }}
|
||||
git push origin ${{ steps.plan.outputs.branch_name }}
|
||||
|
||||
- name: Rebase Existing Branch onto next
|
||||
if: steps.plan.outputs.mode == 'update'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
# Bring the release branch up to date with its parent (next),
|
||||
# replaying any release-branch-only commits on top of the latest next.
|
||||
git checkout ${{ steps.plan.outputs.branch_name }}
|
||||
git fetch origin next
|
||||
git rebase origin/next
|
||||
git push --force-with-lease origin ${{ steps.plan.outputs.branch_name }}
|
||||
|
||||
- name: Trigger Release Workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh workflow run services-www-releases-required.yml --ref ${{ steps.plan.outputs.branch_name }}
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- 'packages/i18n/**'
|
||||
- '.github/workflows/services-www-next-required.yml'
|
||||
- 'pnpm-lock.yaml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-www-${{ github.ref_name }}
|
||||
@@ -330,17 +331,25 @@ jobs:
|
||||
path: services/www/rsdoctor/
|
||||
retention-days: 30
|
||||
|
||||
release-docker:
|
||||
name: Release - Docker Image
|
||||
needs: build-www
|
||||
build-docker:
|
||||
name: Build - Docker Image
|
||||
needs: [lint-www, typecheck-www, test-www, test-storybook, build-www]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
short_sha: ${{ steps.vars.outputs.short_sha }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Calculate Short SHA
|
||||
id: vars
|
||||
run: |
|
||||
SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)"
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -348,18 +357,8 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}/www
|
||||
tags: |
|
||||
type=raw,value=next
|
||||
type=sha,format=short
|
||||
|
||||
- name: Compute short SHA
|
||||
id: vars
|
||||
run: echo "short_sha=sha-$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -367,8 +366,121 @@ jobs:
|
||||
context: .
|
||||
file: ci/docker/www/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ghcr.io/${{ github.repository }}/www:${{ steps.vars.outputs.short_sha }}
|
||||
build-args: |
|
||||
COMMIT_SHA=${{ steps.vars.outputs.short_sha }}
|
||||
VERSION=${{ steps.vars.outputs.short_sha }}
|
||||
|
||||
release-tag-docker:
|
||||
name: Release - Tag & Docker
|
||||
needs: build-docker
|
||||
runs-on: ubuntu-latest
|
||||
environment: services-www-releases-publish
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
outputs:
|
||||
version: ${{ steps.calver.outputs.version }}
|
||||
short_sha: ${{ needs.build-docker.outputs.short_sha }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Calculate CalVer Version
|
||||
id: calver
|
||||
run: |
|
||||
VERSION=$(bash ci/scripts/calver-release.sh)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create and Push Tag
|
||||
run: |
|
||||
TAG_NAME="${{ steps.calver.outputs.version }}"
|
||||
git tag "$TAG_NAME"
|
||||
git push origin "$TAG_NAME"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Tag and Push Release Docker Image
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
ghcr.io/${{ github.repository }}/www:${{ needs.build-docker.outputs.short_sha }} \
|
||||
--tag ghcr.io/${{ github.repository }}/www:${{ steps.calver.outputs.version }}
|
||||
|
||||
deploy:
|
||||
name: Deploy - Kubernetes (GitOps)
|
||||
needs: release-tag-docker
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: services-www-releases-deploy
|
||||
url: https://ermnvldmr.com
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: next
|
||||
token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Kustomize
|
||||
uses: imranismail/setup-kustomize@v2
|
||||
with:
|
||||
kustomize-version: "5.3.0"
|
||||
|
||||
- name: Deploy via GitOps
|
||||
run: |
|
||||
WWW_TAG="${{ needs.release-tag-docker.outputs.short_sha }}"
|
||||
bash ci/scripts/deploy-gitops.sh www "${WWW_TAG}"
|
||||
|
||||
verify-rollout:
|
||||
name: Verify - Production Rollout
|
||||
needs: [release-tag-docker, deploy]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Await production rollout
|
||||
run: |
|
||||
TARGET_COMMIT="${{ needs.release-tag-docker.outputs.short_sha }}"
|
||||
ENDPOINT="https://www.ermnvldmr.com/version.json"
|
||||
|
||||
echo "========================================================"
|
||||
echo "Verifying production deployment on $ENDPOINT"
|
||||
echo "Expected commit: $TARGET_COMMIT"
|
||||
echo "========================================================"
|
||||
|
||||
MAX_ATTEMPTS=45
|
||||
ATTEMPT=1
|
||||
|
||||
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
||||
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
|
||||
|
||||
RESPONSE=$(curl -sfL --max-time 10 -H "User-Agent: GitHub-Actions-Rollout-Verifier" "$ENDPOINT" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RESPONSE" ]; then
|
||||
LIVE_COMMIT=$(echo "$RESPONSE" | jq -r '.commit // empty')
|
||||
|
||||
echo " Live commit: ${LIVE_COMMIT:-<none>}"
|
||||
|
||||
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ]; then
|
||||
echo "✅ Success! Production has successfully rolled out $TARGET_COMMIT."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo " Endpoint did not return valid JSON or timed out."
|
||||
fi
|
||||
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo "❌ Deployment verification timed out after 7.5 minutes."
|
||||
exit 1
|
||||
@@ -1,382 +0,0 @@
|
||||
name: services/www - releases/dispatch-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_ci:
|
||||
description: 'Skip CI checks (fast build, push & GitOps deploy)'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
setup-stage:
|
||||
name: Setup - Stage
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
|
||||
- name: Setup Node.js with pnpm cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache pnpm store
|
||||
id: cache-pnpm-store
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
build-configs:
|
||||
name: Build - Configs
|
||||
needs: setup-stage
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build Rsbuild config
|
||||
run: pnpm --filter @ermnvldmr/rsbuild-config build
|
||||
- name: Build Storybook config
|
||||
run: pnpm --filter @ermnvldmr/storybook-config build
|
||||
- name: Build i18n
|
||||
run: pnpm --filter @ermnvldmr/i18n build
|
||||
- name: Upload Config Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: |
|
||||
packages/rsbuild-config/dist
|
||||
packages/storybook-config/dist
|
||||
packages/i18n/dist
|
||||
retention-days: 1
|
||||
|
||||
lint-www:
|
||||
name: Lint - WWW
|
||||
needs: build-configs
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Lint WWW service
|
||||
run: pnpm --filter @ermnvldmr/www lint
|
||||
|
||||
typecheck-www:
|
||||
name: Typecheck - WWW
|
||||
needs: build-configs
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Typecheck WWW service
|
||||
run: pnpm --filter @ermnvldmr/www typecheck
|
||||
|
||||
build-storybook:
|
||||
name: Build - Storybook
|
||||
needs: build-configs
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Build WWW Storybook
|
||||
run: pnpm --filter @ermnvldmr/www build-storybook
|
||||
- name: Upload Storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: services/www/storybook-static/
|
||||
retention-days: 1
|
||||
|
||||
test-www:
|
||||
name: Test - Unit
|
||||
needs: build-configs
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Test WWW service
|
||||
run: pnpm --filter @ermnvldmr/www test:coverage
|
||||
|
||||
test-storybook:
|
||||
name: Test - Storybook
|
||||
needs: build-storybook
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Install Playwright browsers
|
||||
run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium
|
||||
- name: Download Storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: services/www/storybook-static/
|
||||
- name: Serve Storybook and run tests
|
||||
run: pnpm --filter @ermnvldmr/www test:storybook:ci
|
||||
|
||||
build-www:
|
||||
name: Build - WWW Service
|
||||
needs: build-configs
|
||||
if: ${{ !inputs.skip_ci }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Build WWW service
|
||||
run: pnpm --filter @ermnvldmr/www build
|
||||
|
||||
release-tag-docker:
|
||||
name: Release - Tag & Docker
|
||||
needs: [lint-www, typecheck-www, test-www, test-storybook, build-www]
|
||||
if: ${{ always() && (inputs.skip_ci || (!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled'))) }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: services-www-releases-publish
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Calculate Release Version
|
||||
id: version
|
||||
run: |
|
||||
RELEASE_VERSION=$(bash ci/scripts/semantic-release.sh release ${{ github.ref_name }})
|
||||
# Extract MAJOR.MINOR for tagging
|
||||
MAJOR_MINOR=$(echo "${{ github.ref_name }}" | cut -d/ -f3 | sed 's/^v//')
|
||||
MAJOR=$(echo "$MAJOR_MINOR" | cut -d. -f1)
|
||||
SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)"
|
||||
echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "major_minor=$MAJOR_MINOR" >> $GITHUB_OUTPUT
|
||||
echo "major=$MAJOR" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create and Push Tag
|
||||
run: |
|
||||
TAG_NAME="releases/www/v${{ steps.version.outputs.version }}"
|
||||
git tag $TAG_NAME
|
||||
git push origin $TAG_NAME
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}/www
|
||||
tags: |
|
||||
type=raw,value=v${{ steps.version.outputs.version }}
|
||||
type=raw,value=v${{ steps.version.outputs.major_minor }}
|
||||
type=raw,value=v${{ steps.version.outputs.major }}
|
||||
type=raw,value=latest
|
||||
type=sha,format=short
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ci/docker/www/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
COMMIT_SHA=${{ steps.version.outputs.short_sha }}
|
||||
VERSION=v${{ steps.version.outputs.version }}
|
||||
|
||||
deploy:
|
||||
name: Deploy - Kubernetes (GitOps)
|
||||
needs: release-tag-docker
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: services-www-releases-deploy
|
||||
url: https://ermnvldmr.com
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: next
|
||||
token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Kustomize
|
||||
uses: imranismail/setup-kustomize@v2
|
||||
with:
|
||||
kustomize-version: "5.3.0"
|
||||
|
||||
- name: Deploy via GitOps
|
||||
run: |
|
||||
WWW_TAG="sha-$(echo ${{ github.sha }} | cut -c1-7)"
|
||||
bash ci/scripts/deploy-gitops.sh www "${WWW_TAG}"
|
||||
|
||||
verify-rollout:
|
||||
name: Verify - Production Rollout
|
||||
needs: [release-tag-docker, deploy]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Await production rollout
|
||||
run: |
|
||||
TARGET_COMMIT="sha-$(echo ${{ github.sha }} | cut -c1-7)"
|
||||
TARGET_VERSION="v${{ needs.release-tag-docker.outputs.version }}"
|
||||
ENDPOINT="https://www.ermnvldmr.com/version.json"
|
||||
|
||||
echo "========================================================"
|
||||
echo "Verifying production deployment on $ENDPOINT"
|
||||
echo "Expected commit: $TARGET_COMMIT"
|
||||
echo "Expected version: $TARGET_VERSION"
|
||||
echo "========================================================"
|
||||
|
||||
MAX_ATTEMPTS=45
|
||||
ATTEMPT=1
|
||||
|
||||
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
||||
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
|
||||
|
||||
RESPONSE=$(curl -sfL --max-time 10 -H "User-Agent: GitHub-Actions-Rollout-Verifier" "$ENDPOINT" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RESPONSE" ]; then
|
||||
LIVE_COMMIT=$(echo "$RESPONSE" | jq -r '.commit // empty')
|
||||
LIVE_VERSION=$(echo "$RESPONSE" | jq -r '.version // empty')
|
||||
|
||||
echo " Live commit: ${LIVE_COMMIT:-<none>}"
|
||||
echo " Live version: ${LIVE_VERSION:-<none>}"
|
||||
|
||||
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ] || [ "$LIVE_VERSION" = "$TARGET_VERSION" ]; then
|
||||
echo "✅ Success! Production has successfully rolled out $TARGET_VERSION ($TARGET_COMMIT)."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo " Endpoint did not return valid JSON or timed out."
|
||||
fi
|
||||
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo "❌ Deployment verification timed out after 7.5 minutes."
|
||||
exit 1
|
||||
@@ -1,278 +0,0 @@
|
||||
name: services/www - releases/required
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['releases/www/**']
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-www-rel-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
setup-stage:
|
||||
name: Setup - Stage
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
|
||||
- name: Setup Node.js with pnpm cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache pnpm store
|
||||
id: cache-pnpm-store
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
build-configs:
|
||||
name: Build - Configs
|
||||
needs: setup-stage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build Rsbuild config
|
||||
run: pnpm --filter @ermnvldmr/rsbuild-config build
|
||||
- name: Build Storybook config
|
||||
run: pnpm --filter @ermnvldmr/storybook-config build
|
||||
- name: Build i18n
|
||||
run: pnpm --filter @ermnvldmr/i18n build
|
||||
- name: Upload Config Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: |
|
||||
packages/rsbuild-config/dist
|
||||
packages/storybook-config/dist
|
||||
packages/i18n/dist
|
||||
retention-days: 1
|
||||
|
||||
lint-www:
|
||||
name: Lint - WWW
|
||||
needs: build-configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Lint WWW service
|
||||
run: pnpm --filter @ermnvldmr/www lint
|
||||
|
||||
typecheck-www:
|
||||
name: Typecheck - WWW
|
||||
needs: build-configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Typecheck WWW service
|
||||
run: pnpm --filter @ermnvldmr/www typecheck
|
||||
|
||||
build-storybook:
|
||||
name: Build - Storybook
|
||||
needs: build-configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Build WWW Storybook
|
||||
run: pnpm --filter @ermnvldmr/www build-storybook
|
||||
- name: Upload Storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: services/www/storybook-static/
|
||||
retention-days: 1
|
||||
|
||||
test-www:
|
||||
name: Test - Unit
|
||||
needs: build-configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Test WWW service
|
||||
run: pnpm --filter @ermnvldmr/www test:coverage
|
||||
|
||||
test-storybook:
|
||||
name: Test - Storybook
|
||||
needs: build-storybook
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Install Playwright browsers
|
||||
run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium
|
||||
- name: Download Storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: services/www/storybook-static/
|
||||
- name: Serve Storybook and run tests
|
||||
run: pnpm --filter @ermnvldmr/www test:storybook:ci
|
||||
|
||||
build-www:
|
||||
name: Build - WWW Service
|
||||
needs: build-configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Download Config Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: config-artifacts
|
||||
path: packages
|
||||
- name: Build WWW service
|
||||
run: pnpm --filter @ermnvldmr/www build
|
||||
|
||||
release-rc-docker:
|
||||
name: Release - RC Docker Image
|
||||
needs: build-www
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Calculate RC Version
|
||||
id: version
|
||||
run: |
|
||||
RC_VERSION=$(bash ci/scripts/semantic-release.sh rc ${{ github.ref_name }})
|
||||
echo "rc_version=$RC_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}/www
|
||||
tags: |
|
||||
type=raw,value=v${{ steps.version.outputs.rc_version }}
|
||||
type=sha,format=short
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ci/docker/www/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -123,7 +123,7 @@ ermnvldmr.com/
|
||||
- **DO NOT** use self-referential package imports within workspace packages; use relative paths.
|
||||
- **DO NOT** suppress linter or compiler errors with `@ts-ignore`, `@ts-nocheck`, or inline eslint disables without explicit user approval.
|
||||
- **DO NOT** commit unencrypted `.env` files or secret keys (`.env.keys`).
|
||||
- **DO NOT** run `git commit` or execute automated git commits under any circumstances.
|
||||
- **DO NOT** run destructive Git operations (such as force pushing to `main`/`next` or dropping uncommitted user work) without authorization.
|
||||
|
||||
## 5. Critical Workflows & Safety Directives
|
||||
|
||||
@@ -143,5 +143,4 @@ The agent MUST NOT modify the following files or directories unless explicitly r
|
||||
- `.env.keys` (untracked decryption keys).
|
||||
- `pnpm-lock.yaml` (dependencies must be managed exclusively through `pnpm` CLI commands).
|
||||
- `LICENSE` file.
|
||||
- `ci/` deployment configurations, Dockerfiles, and GitHub Actions workflows.
|
||||
- `services/docs/content/` and `services/www/content/` creative and literary works.
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
YEAR_MONTH=$(date -u +'%Y.%m')
|
||||
git fetch --tags --quiet 2>/dev/null || true
|
||||
|
||||
# Find highest MICRO version for current YYYY.MM (matching tags formatted like YYYY.MM.MICRO)
|
||||
LATEST_TAG=$(git tag -l "${YEAR_MONTH}.*" | sort -V | tail -n1 || echo "")
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
MICRO=0
|
||||
else
|
||||
LATEST_MICRO="${LATEST_TAG#"${YEAR_MONTH}."}"
|
||||
if [[ "$LATEST_MICRO" =~ ^[0-9]+$ ]]; then
|
||||
MICRO=$((LATEST_MICRO + 1))
|
||||
else
|
||||
MICRO=0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${YEAR_MONTH}.${MICRO}"
|
||||
@@ -1,116 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
MODE=$1
|
||||
TARGET=$2
|
||||
INCREMENT_MAJOR=${3:-false}
|
||||
|
||||
if [ -z "$MODE" ] || [ -z "$TARGET" ]; then
|
||||
echo "Usage: $0 <mode: checkout|rc|release> <target: service_path|branch_name> [increment_major: true|false]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case $MODE in
|
||||
checkout)
|
||||
SERVICE_PATH=$TARGET
|
||||
SERVICE_NAME=$(basename "$SERVICE_PATH")
|
||||
|
||||
# Ensure we have the latest tags and branches
|
||||
git fetch --all --tags --quiet 2>/dev/null || true
|
||||
|
||||
BRANCH_NAME=$(node -e '
|
||||
const { execSync } = require("child_process");
|
||||
const serviceName = process.argv[1];
|
||||
const servicePath = process.argv[2];
|
||||
const incrementMajor = process.argv[3] === "true" || process.argv[3] === "major" || process.argv[3] === "1";
|
||||
|
||||
let rawBranchesAndTags = "";
|
||||
try {
|
||||
const remoteBranches = execSync(`git branch -r --list "origin/releases/${serviceName}/v*"`, { encoding: "utf8" });
|
||||
const localBranches = execSync(`git branch --list "releases/${serviceName}/v*"`, { encoding: "utf8" });
|
||||
const tags = execSync(`git tag -l "releases/${serviceName}/v*"`, { encoding: "utf8" });
|
||||
rawBranchesAndTags = `${remoteBranches}\n${localBranches}\n${tags}`;
|
||||
} catch (e) {}
|
||||
|
||||
const versionMatches = Array.from(rawBranchesAndTags.matchAll(/releases\/[^\/]+\/v([0-9]+)\.([0-9]+)/g));
|
||||
|
||||
if (versionMatches.length === 0) {
|
||||
let pkgVersion = "1.0";
|
||||
try {
|
||||
pkgVersion = require(`./${servicePath}/package.json`).version.split(".").slice(0, 2).join(".");
|
||||
} catch (e) {}
|
||||
console.log(`releases/${serviceName}/v${pkgVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let maxMajor = 1;
|
||||
let maxMinor = 0;
|
||||
for (const match of versionMatches) {
|
||||
const maj = parseInt(match[1], 10);
|
||||
const min = parseInt(match[2], 10);
|
||||
if (!isNaN(maj) && !isNaN(min)) {
|
||||
if (maj > maxMajor || (maj === maxMajor && min > maxMinor)) {
|
||||
maxMajor = maj;
|
||||
maxMinor = min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (incrementMajor) {
|
||||
console.log(`releases/${serviceName}/v${maxMajor + 1}.0`);
|
||||
} else {
|
||||
console.log(`releases/${serviceName}/v${maxMajor}.${maxMinor + 1}`);
|
||||
}
|
||||
' "$SERVICE_NAME" "$SERVICE_PATH" "$INCREMENT_MAJOR")
|
||||
|
||||
echo "$BRANCH_NAME"
|
||||
;;
|
||||
rc|release)
|
||||
BRANCH_NAME=$TARGET
|
||||
# Extract service name and major.minor from branch: releases/www/v1.2
|
||||
if [[ ! $BRANCH_NAME =~ ^releases/([^/]+)/v([0-9]+\.[0-9]+)$ ]]; then
|
||||
echo "Error: Branch name must follow 'releases/<service>/v<major>.<minor>' format"
|
||||
exit 1
|
||||
fi
|
||||
SERVICE_NAME="${BASH_REMATCH[1]}"
|
||||
MAJOR_MINOR="${BASH_REMATCH[2]}"
|
||||
|
||||
TAG_PREFIX="releases/$SERVICE_NAME/v$MAJOR_MINOR"
|
||||
|
||||
# Ensure we have the latest tags
|
||||
git fetch --tags --quiet
|
||||
|
||||
# Find the latest patch version for this Major.Minor
|
||||
LATEST_TAG=$(git tag -l "$TAG_PREFIX.*" | grep -v "\-rc" | sort -V | tail -n1 || echo "")
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
PATCH=0
|
||||
else
|
||||
LATEST_VERSION=${LATEST_TAG#"$TAG_PREFIX."}
|
||||
PATCH=$((LATEST_VERSION + 1))
|
||||
fi
|
||||
|
||||
VERSION_TAG="$MAJOR_MINOR.$PATCH"
|
||||
|
||||
if [ "$MODE" == "release" ]; then
|
||||
echo "$VERSION_TAG"
|
||||
else
|
||||
# Calculate RC based on commit count since last tag
|
||||
# This ensures unique, increasing RC numbers without needing to tag every RC
|
||||
LAST_SERVICE_TAG=$(git describe --tags --match "releases/$SERVICE_NAME/*" --abbrev=0 HEAD 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$LAST_SERVICE_TAG" ]; then
|
||||
COMMITS_COUNT=$(git rev-list --count HEAD)
|
||||
else
|
||||
COMMITS_COUNT=$(git rev-list --count "$LAST_SERVICE_TAG"..HEAD)
|
||||
fi
|
||||
|
||||
RC=$((COMMITS_COUNT + 1))
|
||||
echo "$VERSION_TAG-rc.$RC"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown mode: $MODE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -39,10 +39,10 @@ ci/k8s/
|
||||
* **Workflow**: `.github/workflows/services-docs-next-required.yml`.
|
||||
* **Action**: Builds Docker image, updates `ci/k8s/apps/docs/kustomization.yaml`, commits to `next`, and runs automated `verify-rollout`.
|
||||
|
||||
2. **`services/www` (Semantic Release Dispatch)**:
|
||||
* **Trigger**: Dispatched via `.github/workflows/services-www-releases-dispatch-release.yml`.
|
||||
* **Standard Mode**: Runs full verification suite (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Tags git release ➔ Pushes image to GHCR ➔ Updates GitOps tag ➔ Runs `verify-rollout`.
|
||||
* **Fast-Track Mode (`skip_ci: true`)**: Bypasses test suites for emergency deploys and rollbacks, immediately building the image, updating GitOps, and verifying the production rollout.
|
||||
2. **`services/www` (Trunk-Based CD with CalVer Release Gate)**:
|
||||
* **Trigger**: Push to `next` modifying `services/www/**` or workspace dependencies (`packages/ui/**`, `packages/stl/**`, `packages/i18n/**`), or manual `workflow_dispatch`.
|
||||
* **Workflow**: `.github/workflows/services-www-next-required.yml`.
|
||||
* **Pipeline**: Full validation suite (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Builds Docker image with `sha-<commit>` tag ➔ Approval gate (`services-www-releases-publish`) tags CalVer `YYYY.MM.MICRO` in Git & GHCR ➔ Approval gate (`services-www-releases-deploy`) updates GitOps tag ➔ Runs `verify-rollout`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -39,10 +39,10 @@ ci/k8s/
|
||||
* **Workflow**: `.github/workflows/services-docs-next-required.yml`.
|
||||
* **Действие**: Сборка Docker-образа, обновление `ci/k8s/apps/docs/kustomization.yaml`, коммит в `next` и запуск задачи `verify-rollout`.
|
||||
|
||||
2. **`services/www` (Семантический релиз через Dispatch)**:
|
||||
* **Триггер**: Запуск вручную через `.github/workflows/services-www-releases-dispatch-release.yml`.
|
||||
* **Стандартный режим**: Полный цикл тестов (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Git-тег релиза ➔ Пуш в GHCR ➔ Обновление тега в GitOps ➔ Проверка через `verify-rollout`.
|
||||
* **Быстрый режим (`skip_ci: true`)**: Пропуск тестов для экстренных деплоев и откатов с мгновенной сборкой образа, пушем в GitOps и верификацией.
|
||||
2. **`services/www` (Trunk-Based CD с релизным гейтом CalVer)**:
|
||||
* **Триггер**: Push в ветку `next` с изменениями в `services/www/**` или зависимых пакетах (`packages/ui/**`, `packages/stl/**`, `packages/i18n/**`), либо ручной `workflow_dispatch`.
|
||||
* **Workflow**: `.github/workflows/services-www-next-required.yml`.
|
||||
* **Пайплайн**: Полный цикл тестов и проверок (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Сборка Docker-образа с тегом `sha-<commit>` ➔ Гейт подтверждения (`services-www-releases-publish`) выставляет тег CalVer `YYYY.MM.MICRO` в Git и GHCR ➔ Гейт подтверждения (`services-www-releases-deploy`) обновляет GitOps ➔ Проверка раскатки через `verify-rollout`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user