diff --git a/.github/workflows/notify.yml b/.github/workflows/notify.yml deleted file mode 100644 index 20c7277..0000000 --- a/.github/workflows/notify.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Send Feishu Notification - -on: - workflow_call: - inputs: - message: - description: "Notification message content" - required: true - type: string - msg_type: - description: "Message type (text, post, or interactive)" - required: false - type: string - default: "text" - title: - description: "Message title for post/interactive messages" - required: false - type: string - outputs: - status: - description: "Notification delivery status (success/failure)" - value: ${{ jobs.notify.outputs.status }} - timestamp: - description: "ISO 8601 timestamp of notification send attempt" - value: ${{ jobs.notify.outputs.timestamp }} - response: - description: "Webhook response content or error message" - value: ${{ jobs.notify.outputs.response }} - secrets: - FEISHU_WEBHOOK_URL: - required: true - -permissions: - contents: read - -jobs: - notify: - name: Send Notification - runs-on: ubuntu-latest - outputs: - status: ${{ steps.result.outputs.status }} - timestamp: ${{ steps.result.outputs.timestamp }} - response: ${{ steps.result.outputs.response }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Install tsx - run: npm install -g tsx - - - name: Send notification - id: notification - env: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - FEISHU_MESSAGE: ${{ inputs.message }} - FEISHU_MSG_TYPE: ${{ inputs.msg_type }} - FEISHU_TITLE: ${{ inputs.title }} - run: tsx src/feishu.ts > output.json - - - name: Set outputs - id: result - run: | - STATUS=$(node -e "console.log(JSON.parse(require('fs').readFileSync('output.json')).status)") - TIMESTAMP=$(node -e "console.log(JSON.parse(require('fs').readFileSync('output.json')).timestamp)") - RESPONSE=$(node -e "console.log(JSON.parse(require('fs').readFileSync('output.json')).response)") - echo "status=$STATUS" >> $GITHUB_OUTPUT - echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT - echo "response=$RESPONSE" >> $GITHUB_OUTPUT diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 0000000..4f74eaf --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,80 @@ +name: PR Validation + +on: + pull_request: + branches: [main] + +jobs: + validate: + name: Validate TypeScript + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Build TypeScript + run: npm run build + + - name: Test action execution + run: | + # Test with missing webhook (should return failure but not crash) + FEISHU_MESSAGE="Test message" HAGI_ACTION_MODE="true" npx tsx src/feishu.ts + + - name: Verify package.json + run: | + # Verify tsx is in dependencies + if ! grep -q '"tsx"' package.json; then + echo "Error: tsx not found in package.json dependencies" + exit 1 + fi + + # Verify build script exists + if ! grep -q '"build"' package.json; then + echo "Error: build script not found in package.json" + exit 1 + fi + + - name: Verify action.yml + run: | + # Verify action.yml exists + if [ ! -f "action.yml" ]; then + echo "Error: action.yml not found" + exit 1 + fi + + # Verify tsx is used in action.yml + if ! grep -q 'npx tsx src/feishu.ts' action.yml; then + echo "Error: action.yml doesn't use tsx for execution" + exit 1 + fi + + - name: Verify source files + run: | + # Verify required source files exist + if [ ! -f "src/feishu.ts" ]; then + echo "Error: src/feishu.ts not found" + exit 1 + fi + + if [ ! -f "src/types.ts" ]; then + echo "Error: src/types.ts not found" + exit 1 + fi + + if [ ! -f "src/index.ts" ]; then + echo "Error: src/index.ts not found" + exit 1 + fi + + - name: Check TypeScript compilation + run: | + # Ensure no TypeScript errors + npx tsc --noEmit diff --git a/.github/workflows/test-notify.yml b/.github/workflows/test-notify.yml index a228c2d..4333958 100644 --- a/.github/workflows/test-notify.yml +++ b/.github/workflows/test-notify.yml @@ -28,24 +28,22 @@ permissions: jobs: test-notify: name: Test Notification - uses: ./.github/workflows/notify.yml - with: - message: ${{ inputs.message }} - msg_type: ${{ inputs.msg_type }} - title: ${{ inputs.title }} - secrets: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - - show-result: - name: Show Result - needs: test-notify runs-on: ubuntu-latest - if: always() steps: + - id: notification + uses: ./ # Uses action.yml from the current repository + with: + message: ${{ inputs.message }} + msg_type: ${{ inputs.msg_type }} + title: ${{ inputs.title }} + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + - name: Display notification result + if: always() run: | echo "### Notification Result" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Status**: ${{ needs.test-notify.outputs.status }}" >> $GITHUB_STEP_SUMMARY - echo "- **Timestamp**: ${{ needs.test-notify.outputs.timestamp }}" >> $GITHUB_STEP_SUMMARY - echo "- **Response**: ${{ needs.test-notify.outputs.response }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ${{ steps.notification.outputs.status }}" >> $GITHUB_STEP_SUMMARY + echo "- **Timestamp**: ${{ steps.notification.outputs.timestamp }}" >> $GITHUB_STEP_SUMMARY + echo "- **Response**: ${{ steps.notification.outputs.response }}" >> $GITHUB_STEP_SUMMARY diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..15f8030 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.0.0] - 2025-02-09 + +### Added +- GitHub Composite Action (`action.yml`) for Feishu notifications +- Action mode support in `runCli()` to prevent action failures when notifications fail +- `tsx` as runtime dependency for direct TypeScript execution + +### Changed +- **BREAKING**: Converted from reusable workflow to composite action +- **BREAKING**: Updated usage syntax from `uses: ./.github/workflows/notify.yml@main` to `uses: HagiCode-org/haginotifier@v1` +- Secrets are now passed via `env:` instead of `secrets:` in workflow syntax +- Outputs are accessed via `steps..outputs.*` instead of `needs..outputs.*` + +### Removed +- Deprecated `.github/workflows/notify.yml` reusable workflow + +### Migration Guide + +If you were using the old reusable workflow syntax, update your workflows: + +**Old syntax:** +```yaml +jobs: + notify: + uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main + with: + message: 'Deployment successful!' + secrets: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} +``` + +**New syntax:** +```yaml +jobs: + notify: + runs-on: ubuntu-latest + steps: + - uses: HagiCode-org/haginotifier@v1 + with: + message: 'Deployment successful!' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} +``` + +See README.md for more details and examples. diff --git a/README.md b/README.md index e94189e..f6807f6 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # haginotifier -> Reusable GitHub Actions workflow for sending notifications to Feishu (飞书) +> GitHub Action for sending notifications to Feishu (飞书) -A simple, reusable GitHub Actions workflow that allows any repository to send notifications to Feishu via webhook. This provides a unified notification mechanism across your organization, eliminating the need to duplicate notification logic in each repository. +A simple GitHub Action that allows any repository to send notifications to Feishu via webhook. This provides a unified notification mechanism across your organization, eliminating the need to duplicate notification logic in each repository. ## Features -- **Reusable Workflow**: Call from any repository using standard `uses:` syntax +- **GitHub Composite Action**: Use standard GitHub Actions syntax with version pinning - **Multiple Message Types**: Support for text, rich text (post), and interactive card messages - **Simple Configuration**: Just provide a webhook URL and message content - **Standardized Output**: Returns status, timestamp, and response data for downstream processing -- **ESM Module**: Built with modern TypeScript and ESM for Node.js 18+ +- **Fast Execution**: Pre-compiled JavaScript with no runtime TypeScript compilation overhead ## Usage @@ -21,11 +21,13 @@ Send a simple text message: ```yaml jobs: notify: - uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main - with: - message: 'Deployment successful!' - secrets: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + runs-on: ubuntu-latest + steps: + - uses: HagiCode-org/haginotifier@v1 + with: + message: 'Deployment successful!' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} ``` ### Rich Text Message @@ -35,13 +37,15 @@ Send a rich text message with a title: ```yaml jobs: notify: - uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main - with: - message: 'The production deployment has been completed successfully.' - msg_type: 'post' - title: 'Deployment Notification' - secrets: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + runs-on: ubuntu-latest + steps: + - uses: HagiCode-org/haginotifier@v1 + with: + message: 'The production deployment has been completed successfully.' + msg_type: 'post' + title: 'Deployment Notification' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} ``` ### Interactive Card Message @@ -51,13 +55,15 @@ Send an interactive card message: ```yaml jobs: notify: - uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main - with: - message: 'Build #123 completed in 5 minutes' - msg_type: 'interactive' - title: 'CI/CD Pipeline Status' - secrets: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + runs-on: ubuntu-latest + steps: + - uses: HagiCode-org/haginotifier@v1 + with: + message: 'Build #123 completed in 5 minutes' + msg_type: 'interactive' + title: 'CI/CD Pipeline Status' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} ``` ### Using Outputs @@ -67,20 +73,19 @@ Access the notification result in subsequent steps: ```yaml jobs: notify: - uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main - with: - message: 'Test notification' - secrets: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - check-result: - needs: notify runs-on: ubuntu-latest steps: + - id: notification + uses: HagiCode-org/haginotifier@v1 + with: + message: 'Test notification' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - name: Check notification status run: | - echo "Status: ${{ needs.notify.outputs.status }}" - echo "Timestamp: ${{ needs.notify.outputs.timestamp }}" - echo "Response: ${{ needs.notify.outputs.response }}" + echo "Status: ${{ steps.notification.outputs.status }}" + echo "Timestamp: ${{ steps.notification.outputs.timestamp }}" + echo "Response: ${{ steps.notification.outputs.response }}" ``` ## Input Parameters @@ -91,10 +96,10 @@ jobs: | `msg_type` | No | string | Message type: `text`, `post`, or `interactive` | `text` | | `title` | No | string | Message title for post/interactive messages | - | -## Secrets +## Environment Variables -| Secret | Required | Description | -|--------|----------|-------------| +| Variable | Required | Description | +|----------|----------|-------------| | `FEISHU_WEBHOOK_URL` | Yes | Feishu Webhook URL for sending notifications | ## Output Parameters @@ -131,11 +136,13 @@ Configure the webhook URL once at the organization level, and all repositories w ```yaml jobs: notify: - uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main - with: - message: 'Your notification here' - secrets: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + runs-on: ubuntu-latest + steps: + - uses: HagiCode-org/haginotifier@v1 + with: + message: 'Your notification here' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} ``` **Benefits of Organization-level Secrets:** @@ -161,8 +168,71 @@ Each repository manages its own webhook URL. This is suitable for: 3. Select "Custom Bot" and configure it 4. Copy the Webhook URL +### Versioning + +For production use, pin to a specific version tag instead of using `@v1`: + +```yaml +# Pin to major version (gets updates) +- uses: HagiCode-org/haginotifier@v1 + +# Pin to specific version (most stable) +- uses: HagiCode-org/haginotifier@v1.0.0 + +# Use latest (not recommended for production) +- uses: HagiCode-org/haginotifier@main +``` + ## Migration Guide +### Migrating from Reusable Workflow (Breaking Change) + +This action has been converted from a reusable workflow to a composite action. If you were using the old reusable workflow syntax, you need to update your workflow files. + +**Old syntax (Reusable Workflow):** + +```yaml +jobs: + notify: + uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main + with: + message: 'Deployment successful!' + secrets: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + + check-result: + needs: notify + runs-on: ubuntu-latest + steps: + - name: Check status + run: echo "Status: ${{ needs.notify.outputs.status }}" +``` + +**New syntax (Composite Action):** + +```yaml +jobs: + notify: + runs-on: ubuntu-latest + steps: + - id: notification + uses: HagiCode-org/haginotifier@v1 + with: + message: 'Deployment successful!' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + + - name: Check status + run: echo "Status: ${{ steps.notification.outputs.status }}" +``` + +**Key Changes:** +1. Add `runs-on: ubuntu-latest` to the job +2. Add `steps:` array +3. Move `secrets:` to `env:` under the step +4. Use `steps..outputs.` instead of `needs..outputs.` +5. Use `@v1` version tag instead of `@main` branch + ### Migrating from Per-Repository to Organization-level Secret If you currently have the `FEISHU_WEBHOOK_URL` secret configured in multiple repositories, you can migrate to organization-level secrets for easier management. @@ -187,20 +257,12 @@ If you encounter any issues, you can easily rollback: 1. Re-create the `FEISHU_WEBHOOK_URL` secret in individual repositories 2. No changes to workflow files are needed -### 3. Reference a Specific Version - -For production use, pin to a specific version tag instead of `main`: - -```yaml -uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@v1.0.0 -``` - ## Development ### Prerequisites -- Node.js 18 or higher -- tsx (TypeScript executor) +- Node.js 20 or higher +- TypeScript 5+ ### Local Testing @@ -218,12 +280,12 @@ FEISHU_WEBHOOK_URL="your_webhook_url" FEISHU_MESSAGE="Test message" npx tsx src/ haginotifier/ ├── .github/ │ └── workflows/ -│ ├── notify.yml # Reusable workflow definition │ └── test-notify.yml # Manual test workflow ├── src/ │ ├── feishu.ts # Feishu Webhook implementation │ ├── types.ts # TypeScript type definitions -│ └── index.ts # Main entry point +│ └── index.ts # Main module exports +├── action.yml # Composite action definition ├── package.json ├── tsconfig.json └── README.md @@ -231,7 +293,7 @@ haginotifier/ ### Testing -You can test the notification workflow directly from GitHub: +You can test the notification action directly from GitHub: 1. Go to Actions tab in the haginotifier repository 2. Select "Test Notification" workflow @@ -252,7 +314,7 @@ The codebase is designed to be easily extended with additional notification prov 2. Implement a similar `sendNotification` function 3. Add provider-specific types to `src/types.ts` 4. Export from `src/index.ts` -5. Update the workflow to accept a `provider` input parameter +5. Update `action.yml` to accept a `provider` input parameter ## License @@ -284,6 +346,10 @@ A: Yes, you can use both approaches simultaneously. Some repositories can use th A: With organization-level secrets, simply update the secret value in the organization settings. All repositories with access will automatically use the new value. +**Q: What's the difference between the old reusable workflow and the new action?** + +A: The new action is faster (no runtime TypeScript compilation), uses standard GitHub Actions versioning (`@v1`), and follows ecosystem best practices. The old workflow syntax is deprecated and will be removed in a future release. + ## Troubleshooting ### Secret not found error diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..23c6b16 --- /dev/null +++ b/action.yml @@ -0,0 +1,59 @@ +name: 'Send Feishu Notification' +description: 'Send notifications to Feishu (飞书) via webhook' +branding: + icon: 'send' + color: 'blue' + +inputs: + message: + description: 'Notification message content' + required: true + msg_type: + description: 'Message type (text, post, or interactive)' + required: false + default: 'text' + title: + description: 'Message title for post/interactive messages' + required: false + +outputs: + status: + description: 'Notification delivery status' + value: ${{ steps.run.outputs.status }} + timestamp: + description: 'ISO 8601 timestamp' + value: ${{ steps.run.outputs.timestamp }} + response: + description: 'Webhook response or error message' + value: ${{ steps.run.outputs.response }} + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + shell: bash + run: cd ${{ github.action_path }} && npm ci --production + + - name: Run notification + id: run + shell: bash + env: + FEISHU_WEBHOOK_URL: ${{ env.FEISHU_WEBHOOK_URL }} + FEISHU_MESSAGE: ${{ inputs.message }} + FEISHU_MSG_TYPE: ${{ inputs.msg_type }} + FEISHU_TITLE: ${{ inputs.title }} + HAGI_ACTION_MODE: "true" + run: | + cd ${{ github.action_path }} + OUTPUT=$(npx tsx src/feishu.ts) + STATUS=$(echo "$OUTPUT" | grep -o '"status":"[^"]*"' | cut -d'"' -f4) + TIMESTAMP=$(echo "$OUTPUT" | grep -o '"timestamp":"[^"]*"' | cut -d'"' -f4) + RESPONSE=$(echo "$OUTPUT" | grep -o '"response":"[^"]*"' | cut -d'"' -f4 | sed 's/\\"/"/g') + echo "status=$STATUS" >> $GITHUB_OUTPUT + echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT + echo "response=$RESPONSE" >> $GITHUB_OUTPUT diff --git a/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/design.md b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/design.md new file mode 100644 index 0000000..0823876 --- /dev/null +++ b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/design.md @@ -0,0 +1,335 @@ +# Design Document: Convert Reusable Workflow to GitHub Action + +## Context + +The current implementation uses GitHub Actions reusable workflow (`workflow_call` trigger) which requires: +- Full repository checkout during execution +- Runtime installation of tsx for TypeScript execution +- Non-standard invocation syntax (`uses: ./.github/workflows/notify.yml@main`) + +This design documents the conversion to a standard GitHub Composite Action for better performance, user experience, and ecosystem alignment. + +## Goals / Non-Goals + +### Goals +- Eliminate runtime dependencies (tsx) by using pre-compiled JavaScript +- Reduce execution overhead (no full repository checkout needed) +- Align with GitHub Actions ecosystem standards +- Enable proper versioning via Git tags +- Maintain functional parity with current implementation +- Preserve all existing inputs and outputs + +### Non-Goals +- Supporting both workflow and action modes simultaneously (choose one pattern) +- Changing the notification API or Feishu integration logic +- Adding new notification providers in this change +- Publishing to npm registry (distribution via GitHub only) + +## Decisions + +### Decision 1: Use Composite Action Type + +**Choice**: Use `runs: using: 'composite'` in `action.yml` + +**Rationale**: +- Composite actions allow running multiple steps including shell scripts +- Can leverage existing actions like `actions/setup-node@v4` +- No need to bundle everything into a single executable +- Easier to maintain and debug than JavaScript/TypeScript actions +- Can use npm dependencies directly + +**Alternatives considered**: +- **JavaScript action**: Would require bundling all dependencies, more complex build setup +- **Container action**: Overkill for this use case, slower startup time +- **Keep reusable workflow**: Doesn't solve the runtime overhead issues + +### Decision 2: Build to dist/ Directory + +**Choice**: Compile TypeScript to `dist/index.js` and `dist/*.js` for all source files + +**Rationale**: +- Clean separation between source and compiled code +- Can add `dist/` to `.gitignore` for local development +- Build workflow ensures compiled code is committed only on release +- Follows common Node.js package patterns + +**Alternatives considered**: +- **Compile to same directory**: Would mix source and compiled files +- **Use different extension**: `.cjs` or `.mjs` adds complexity + +### Decision 3: Environment Variable Input for Webhook URL + +**Choice**: Pass `FEISHU_WEBHOOK_URL` via `env:` rather than as an action input + +**Rationale**: +- Maintains consistency with current implementation +- Better security practice (secrets should be in environment, not inputs) +- Aligns with GitHub Actions patterns for sensitive data + +### Decision 4: Breaking Change with Migration Guide + +**Choice**: Make this a breaking change and provide clear migration documentation + +**Rationale**: +- Simpler implementation (no need to support both modes) +- Cleaner final codebase +- Clearer user experience (one canonical way to use) +- Migration is straightforward (single workflow syntax change) + +**Alternatives considered**: +- **Support both concurrently**: Adds maintenance burden, confusion +- **Automatic migration**: Not technically possible, requires user action + +## Technical Design + +### Architecture + +```mermaid +graph TB + subgraph "User Workflow" + A[User invokes action] --> B[action.yml] + end + + subgraph "Composite Action" + B --> C[Setup Node.js] + C --> D[Install Dependencies] + D --> E[Execute dist/index.js] + E --> F[Parse outputs] + end + + subgraph "Feishu API" + E --> G[POST webhook_url] + G --> H[Return response] + end + + subgraph "Action Outputs" + F --> I[status: success/failure] + F --> J[timestamp: ISO 8601] + F --> K[response: API response] + end +``` + +### File Structure Changes + +| File Path | Change Type | Reason | +|-----------|-------------|--------| +| `action.yml` | ADD | New action definition (replaces notify.yml) | +| `.github/workflows/notify.yml` | REMOVE | Replaced by composite action | +| `.github/workflows/test-notify.yml` | MODIFY | Update to use new action syntax instead of reusable workflow | +| `dist/index.js` | ADD | Compiled entry point | +| `dist/feishu.js` | ADD | Compiled notification logic | +| `dist/types.js` | ADD | Compiled type definitions (stripped at runtime) | +| `.github/workflows/build.yml` | ADD | CI/CD for automated builds | +| `package.json` | MODIFY | Add build script, prepublishOnly hook | +| `tsconfig.json` | MODIFY | Add outDir configuration | +| `src/action.ts` | ADD | New entry point for action execution (optional) | +| `README.md` | MODIFY | Update usage examples, add migration guide | + +### Input/Output Flow + +```mermaid +sequenceDiagram + participant User + participant Action + participant CompiledCode + participant Feishu + + User->>Action: uses: ...@v1
with: message="..."
env: FEISHU_WEBHOOK_URL + Action->>CompiledCode: node dist/index.js
with env vars + CompiledCode->>CompiledCode: Parse inputs
Build payload + CompiledCode->>Feishu: POST webhook_url + Feishu-->>CompiledCode: Response + CompiledCode->>CompiledCode: Set GITHUB_OUTPUT + Action-->>User: Outputs: status,
timestamp, response +``` + +### Build Process + +```mermaid +flowchart TD + A[Developer pushes code] --> B{Is it a tag?} + B -->|Yes| C[Create release] + B -->|No| D[Normal push] + C --> E[Run build workflow] + D --> E + E --> F[npm run build] + F --> G[tsc compiles to dist/] + G --> H[Commit dist/ files] + H --> I[Push to repository] +``` + +### Build Workflow Definition + +```yaml +name: Build + +on: + push: + branches: [main] + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm ci + - run: npm run build + - run: | + git config user.name github-actions + git config user.email github-actions@github.com + git add dist/ + git diff --staged --quiet || git commit -m "Build: compile TypeScript" + git push +``` + +### Action Definition Structure + +```yaml +name: 'Send Feishu Notification' +description: 'Send notifications to Feishu via webhook' +branding: + icon: 'send' + color: 'blue' + +inputs: + message: + description: 'Notification message content' + required: true + msg_type: + description: 'Message type (text, post, or interactive)' + required: false + default: 'text' + title: + description: 'Message title for post/interactive messages' + required: false + +outputs: + status: + description: 'Notification delivery status' + value: ${{ steps.run.outputs.status }} + timestamp: + description: 'ISO 8601 timestamp' + value: ${{ steps.run.outputs.timestamp }} + response: + description: 'Webhook response or error message' + value: ${{ steps.run.outputs.response }} + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + shell: bash + run: cd ${{ github.action_path }} && npm ci --production + + - name: Run notification + id: run + shell: bash + env: + FEISHU_WEBHOOK_URL: ${{ env.FEISHU_WEBHOOK_URL }} + FEISHU_MESSAGE: ${{ inputs.message }} + FEISHU_MSG_TYPE: ${{ inputs.msg_type }} + FEISHU_TITLE: ${{ inputs.title }} + run: | + OUTPUT=$(node ${{ github.action_path }}/dist/index.js) + echo "status=$(echo $OUTPUT | jq -r '.status')" >> $GITHUB_OUTPUT + echo "timestamp=$(echo $OUTPUT | jq -r '.timestamp')" >> $GITHUB_OUTPUT + echo "response=$(echo $OUTPUT | jq -r '.response')" >> $GITHUB_OUTPUT +``` + +## Risks / Trade-offs + +### Risk 1: Breaking Change for Existing Users + +**Impact**: All users must update their workflow configurations + +**Mitigation**: +- Provide clear migration guide in README +- Consider maintaining old workflow with deprecation notice for 1-2 minor versions +- Announce change through repository communications + +### Risk 2: Build Complexity + +**Impact**: Addition of build step adds complexity to release process + +**Mitigation**: +- Use GitHub Actions for automated builds +- Keep build configuration simple (just `tsc`) +- Document build process for contributors + +### Risk 3: dist/ File Bloat + +**Impact**: Compiled JavaScript files will be in the repository + +**Mitigation**: +- Add `dist/` to `.gitignore` for local development +- Only commit dist/ files via automated build workflow +- Keep source code clean and well-organized + +### Trade-off: No Runtime TypeScript Compilation + +**Benefit**: Faster execution, no tsx dependency + +**Cost**: Requires build step before release, adds dist/ files to repo + +**Decision**: Benefits outweigh costs for this use case + +## Migration Plan + +### Phase 1: Implementation (No User Impact) +1. Create `action.yml` alongside existing `notify.yml` +2. Add build configuration +3. Implement compiled execution path +4. Test new action in isolation + +### Phase 2: Testing +1. Update `test-notify.yml` workflow to use new action syntax +2. Modify test workflow to use action step instead of reusable workflow call +3. Update show-result job to work with action-level outputs +4. Verify all message types work correctly +5. Test with both organization and repository secrets +6. Validate outputs are properly passed + +### Phase 3: Release +1. Create v1.0.0 release tag +2. Update README with new usage examples +3. Add migration guide section +4. Announce to users + +### Phase 4: Deprecation (Optional) +1. Add deprecation notice to `notify.yml` +2. Wait 2-3 minor versions for adoption +3. Remove `notify.yml` in v2.0.0 + +### Rollback Plan + +If issues arise after release: +1. Revert `action.yml` changes +2. Users can continue using `notify.yml` with `@` syntax +3. Fix issues in next release cycle + +## Open Questions + +1. **Should we maintain backward compatibility during transition period?** + - Recommendation: No, provide clear migration guide instead + +2. **Should we publish to npm registry in addition to GitHub?** + - Recommendation: No, keep GitHub-only distribution for simplicity + +3. **Should we add GitHub Actions Toolkit for better output handling?** + - Recommendation: Not needed for this simple use case, manual parsing is sufficient + +4. **What Node.js version should we target?** + - Recommendation: Node.js 20 (current LTS), matches current workflow configuration + +5. **Should we create a separate major version (v2) instead of breaking v1?** + - Recommendation: Treat v1.0.0 as the first action release, old workflow was "beta" diff --git a/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/proposal.md b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/proposal.md new file mode 100644 index 0000000..a1dbb93 --- /dev/null +++ b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/proposal.md @@ -0,0 +1,83 @@ +# Change: Convert Reusable Workflow to GitHub Action + +## Why + +The current reusable workflow implementation has several limitations that impact user experience and maintainability: + +1. **Runtime overhead**: Every execution requires checking out the entire repository, setting up Node.js, and installing tsx globally +2. **Distribution limitations**: Users cannot reference the action using standard GitHub Actions versioning patterns (e.g., `@v1`) +3. **Non-standard pattern**: The reusable workflow approach diverges from GitHub Actions ecosystem conventions for distributable actions +4. **Client-side complexity**: Consuming repositories must use the verbose workflow-specific syntax + +Converting to a proper GitHub Action will align with ecosystem standards, improve performance, and simplify the user experience. + +## What Changes + +- **BREAKING**: Replace `.github/workflows/notify.yml` reusable workflow with `action.yml` composite action +- Add TypeScript compilation step to build `dist/index.js` from `src/` sources +- Update action invocation syntax from `uses: ./.github/workflows/notify.yml@main` to `uses: HagiCode-org/haginotifier@v1` +- Remove runtime dependency on `tsx` - action will use pre-compiled JavaScript +- Add CI/CD workflow for automated building and publishing +- Update all documentation with new usage patterns + +## Migration Path + +Users must update their workflow configurations: + +**Old syntax:** +```yaml +jobs: + notify: + uses: HagiCode-org/haginotifier/.github/workflows/notify.yml@main + with: + message: 'Deployment successful!' + secrets: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} +``` + +**New syntax:** +```yaml +jobs: + notify: + runs-on: ubuntu-latest + steps: + - uses: HagiCode-org/haginotifier@v1 + with: + message: 'Deployment successful!' + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} +``` + +## Impact + +- **Affected specs**: `feishu-notification` (new capability) +- **Affected code**: + - `src/feishu.ts` - may need modifications for action context + - `src/index.ts` - may need new entry point for action execution + - `.github/workflows/notify.yml` - will be deprecated/removed + - `.github/workflows/test-notify.yml` - needs update to use new action syntax + - `package.json` - will need build scripts and dependencies + - `tsconfig.json` - may need updates for build output + - `README.md` - requires comprehensive update for new usage + +## Benefits + +### User Experience +- Cleaner, more intuitive action invocation syntax +- Standard version pinning via Git tags (`@v1`, `@v1.0.0`) +- No need to consume repository to access workflow files + +### Performance +- Faster execution: no checkout, no tsx installation +- Direct execution of compiled JavaScript code + +### Maintainability +- Follows GitHub Actions best practices +- Better alignment with ecosystem patterns +- Easier to add features like branding metadata + +## Risks + +- **Breaking change**: All existing users must update their workflow configurations +- **Transition period**: Need to maintain backward compatibility or provide clear migration guidance +- **Build complexity**: Addition of build step requires CI/CD pipeline changes diff --git a/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/specs/feishu-notification/spec.md b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/specs/feishu-notification/spec.md new file mode 100644 index 0000000..11a0b2c --- /dev/null +++ b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/specs/feishu-notification/spec.md @@ -0,0 +1,98 @@ +## ADDED Requirements + +### Requirement: Composite Action Definition + +The system SHALL provide a GitHub Action defined in `action.yml` using the composite action type. + +#### Scenario: Action metadata is valid + +- **WHEN** a user references the action with `uses: HagiCode-org/haginotifier@v1` +- **THEN** the action metadata is correctly loaded by GitHub Actions +- **AND** all defined inputs and outputs are available + +#### Scenario: Action has proper branding + +- **WHEN** the action appears in the GitHub Marketplace or workflow UI +- **THEN** the action displays with configured icon and color + +### Requirement: Action Inputs + +The action SHALL accept `message`, `msg_type`, and `title` as input parameters. + +#### Scenario: Required message input + +- **WHEN** a user invokes the action +- **THEN** the `message` parameter is required +- **AND** validation fails if `message` is not provided + +#### Scenario: Optional msg_type with default + +- **WHEN** a user invokes the action without specifying `msg_type` +- **THEN** the default value `"text"` is used +- **AND** the action executes without requiring this parameter + +#### Scenario: Optional title parameter + +- **WHEN** a user invokes the action without specifying `title` +- **THEN** the action executes successfully +- **AND** the notification is sent without a title + +### Requirement: Action Outputs + +The action SHALL provide `status`, `timestamp`, and `response` as output parameters. + +#### Scenario: Success status on successful delivery + +- **WHEN** the notification is successfully delivered to Feishu +- **THEN** the `status` output is set to `"success"` +- **AND** subsequent workflow steps can access this value + +#### Scenario: Failure status on delivery error + +- **WHEN** the notification delivery fails (network error, invalid webhook, etc.) +- **THEN** the `status` output is set to `"failure"` +- **AND** the `response` output contains error details + +#### Scenario: Timestamp is always set + +- **WHEN** the action completes execution +- **THEN** the `timestamp` output contains an ISO 8601 formatted timestamp +- **AND** the timestamp reflects when the notification attempt was made + +#### Scenario: Response contains API response + +- **WHEN** the Feishu API returns a response +- **THEN** the `response` output contains the raw API response +- **AND** the response is available for debugging and logging + +### Requirement: Pre-compiled Execution + +The action SHALL execute pre-compiled JavaScript from `dist/index.js` without requiring runtime TypeScript compilation. + +#### Scenario: No tsx dependency at runtime + +- **WHEN** the action executes in a workflow +- **THEN** `tsx` is not installed or required +- **AND** the action uses only Node.js runtime + +#### Scenario: Compiled code is present + +- **WHEN** a release tag is created +- **THEN** the `dist/index.js` file is present in the repository +- **AND** the compiled code is executable by Node.js 18+ + +### Requirement: Versioned Releases + +The action SHALL support semantic versioning through Git tags. + +#### Scenario: Version pinning by tag + +- **WHEN** a user references `HagiCode-org/haginotifier@v1.0.0` +- **THEN** the exact code from that tagged release is executed +- **AND** changes to main branch do not affect the pinned version + +#### Scenario: Major version alias + +- **WHEN** a user references `HagiCode-org/haginotifier@v1` +- **THEN** the latest v1.x release is used +- **AND** breaking v2 changes do not affect v1 users diff --git a/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/tasks.md b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/tasks.md new file mode 100644 index 0000000..ebb3acb --- /dev/null +++ b/openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/tasks.md @@ -0,0 +1,76 @@ +# Implementation Tasks + +## 1. Project Configuration + +- [ ] 1.1 Update `tsconfig.json` to configure `outDir` for compiled output (`dist/`) +- [ ] 1.2 Add build script to `package.json` for TypeScript compilation +- [ ] 1.3 Add `prepublishOnly` script to ensure builds before publishing +- [ ] 1.4 Update `package.json` with proper action metadata (if needed for npm publishing) + +## 2. Build Infrastructure + +- [ ] 2.1 Create `.github/workflows/build.yml` for CI/CD pipeline +- [ ] 2.2 Configure build workflow to run on push to main and on tag creation +- [ ] 2.3 Add automated compilation step in CI workflow +- [ ] 2.4 Add validation step to ensure `dist/index.js` is present + +## 3. Action Definition + +- [ ] 3.1 Create `action.yml` with composite action definition +- [ ] 3.2 Define inputs: `message`, `msg_type`, `title` +- [ ] 3.3 Define outputs: `status`, `timestamp`, `response` +- [ ] 3.4 Add branding metadata (icon, color) +- [ ] 3.5 Configure action to run with `runs: using: 'composite'` + +## 4. Core Implementation + +- [ ] 4.1 Update `src/index.ts` to export action-compatible entry point +- [ ] 4.2 Create or update `src/action.ts` for GitHub Actions execution context +- [ ] 4.3 Ensure `runCli()` function properly handles action environment variables +- [ ] 4.4 Add GITHUB_OUTPUT support for setting action outputs +- [ ] 4.5 Test compiled output with `node dist/index.js` + +## 5. Action Steps + +- [ ] 5.1 Add `actions/setup-node@v4` step for Node.js runtime +- [ ] 5.2 Configure npm install for runtime dependencies +- [ ] 5.3 Add execution step for compiled JavaScript +- [ ] 5.4 Add output parsing step for status, timestamp, response + +## 6. Documentation + +- [ ] 6.1 Update `README.md` with new action usage syntax +- [ ] 6.2 Add migration guide section for existing users +- [ ] 6.3 Create example workflows showing new syntax +- [ ] 6.4 Update input/output documentation tables +- [ ] 6.5 Add versioning documentation (tag-based releases) + +## 7. Testing + +- [ ] 7.1 Test action locally with `act` (if available) +- [ ] 7.2 Create test workflow in repository using new action syntax +- [ ] 7.3 Verify all three message types (text, post, interactive) work correctly +- [ ] 7.4 Verify output parameters are correctly passed to subsequent steps +- [ ] 7.5 Test with both organization-level and repository-level secrets + +## 8. Release Preparation + +- [ ] 8.1 Create first release tag (v1.0.0) +- [ ] 8.2 Verify build workflow creates and publishes artifacts +- [ ] 8.3 Test action invocation with version tag syntax +- [ ] 8.4 Add CHANGELOG.md for release notes + +## 9. Backward Compatibility (Optional) + +- [ ] 9.1 Update `.github/workflows/test-notify.yml` to use new action syntax +- [ ] 9.2 Modify test workflow to use action step instead of reusable workflow call +- [ ] 9.3 Update show-result job to work with action-level outputs +- [ ] 9.4 Decide whether to deprecate or remove old workflow file +- [ ] 9.5 Add deprecation notice to old workflow if keeping it +- [ ] 9.6 Document transition period for existing users + +## 10. Validation + +- [ ] 10.1 Run `openspec validate convert-reusable-workflow-to-github-action --strict` +- [ ] 10.2 Verify all spec deltas are properly formatted +- [ ] 10.3 Ensure at least one scenario per requirement diff --git a/openspec/project.md b/openspec/project.md index 3da5119..364e6be 100644 --- a/openspec/project.md +++ b/openspec/project.md @@ -1,31 +1,204 @@ # Project Context ## Purpose -[Describe your project's purpose and goals] + +haginotifier 是一个可重用的 GitHub Actions 工作流,用于向飞书(Feishu)发送通知。该项目为组织内所有仓库提供统一的通知机制,消除了在每个仓库中重复实现通知逻辑的需要。通过组织级别的密钥配置,可以实现一次配置、多仓库复用的便捷管理方式。 ## Tech Stack -- [List your primary technologies] -- [e.g., TypeScript, React, Node.js] + +- **TypeScript 5.7.2** - 主要开发语言 +- **Node.js 20+** - 运行时环境(ESM 模块) +- **GitHub Actions** - 复合 action 执行平台 +- **飞书 Webhook API** - 通知发送接口 +- **ESM (ES Modules)** - 模块系统(NodeNext) +- **tsx** - TypeScript 执行器(运行时直接执行 TS) ## Project Conventions ### Code Style -[Describe your code style preferences, formatting rules, and naming conventions] + +- **模块系统**:使用 ESM (`"type": "module"`) +- **TypeScript 配置**:NodeNext 模块解析,ES2022 目标 +- **严格模式**:启用 `strict: true` +- **类型导出**:生成 `.d.ts` 声明文件 +- **命名规范**: + - 文件名:kebab-case(如 `feishu.ts`, `types.ts`) + - 函数名:camelCase(如 `sendNotification`, `buildPayload`) + - 类型名:PascalCase(如 `NotificationInput`, `FeishuWebhookPayload`) ### Architecture Patterns -[Document your architectural decisions and patterns] + +- **单一职责**:每个模块专注一个功能领域 + - `feishu.ts` - 飞书 API 集成 + - `types.ts` - 类型定义 + - `index.ts` - 主入口和导出 +- **函数式设计**:纯函数处理数据转换,副作用隔离在 I/O 操作中 +- **错误处理**:统一的错误响应格式,返回结构化的 `NotificationOutput` +- **可扩展性**:设计支持添加其他通知提供商(钉钉、企业微信等) ### Testing Strategy -[Explain your testing approach and requirements] + +- **本地测试**:使用 `npx tsx src/feishu.ts` 直接执行 TypeScript 代码 +- **环境变量测试**:通过环境变量配置 webhook URL 和消息内容 +- **GitHub Actions 测试**:提供 `test-notify.yml` 手动测试工作流 +- **输出验证**:检查 action 返回的 `status`、`timestamp` 和 `response` 参数 ### Git Workflow -[Describe your branching strategy and commit conventions] + +- **分支策略**:主要开发在 `main` 分支 +- **提交规范**: + - 使用简洁的英文提交消息 + - 示例:`update readme`, `feishu`, `update repo path` +- **版本管理**:使用 Git 标签标记版本(如 `v1.0.0`) +- **工作流引用**:推荐使用特定版本标签而非 `main` 分支(生产环境) ## Domain Context -[Add domain-specific knowledge that AI assistants need to understand] + +### 飞书 Webhook 通知 + +飞书支持三种消息类型: + +1. **text** - 纯文本消息 +2. **post** - 富文本消息(支持标题) +3. **interactive** - 交互卡片消息(支持标题和卡片元素) + +### 密钥配置策略 + +项目支持两种密钥配置方式: + +1. **组织级别密钥**(推荐): + - 在组织级别配置一次 + - 通过访问设置控制可用仓库 + - 集中管理,易于维护 + +2. **仓库级别密钥**: + - 每个仓库单独配置 + - 适用于个人账户或需要不同 webhook URL 的场景 + +### 工作流使用模式 + +使用 GitHub Actions 的复合 action 特性: +- Action 定义位于:`action.yml` +- 调用语法:`uses: HagiCode-org/haginotifier@v1` +- 输入参数:`message`, `msg_type`, `title` +- 密钥要求:`FEISHU_WEBHOOK_URL`(通过 `env:` 传递) +- 输出参数:`status`, `timestamp`, `response`(通过 `steps..outputs.*` 访问) ## Important Constraints -[List any technical, business, or regulatory constraints] + +- **Node.js 版本**:要求 Node.js 20 或更高版本 +- **飞书 API 限制**:遵循飞书 Webhook API 的速率限制和消息格式要求 +- **密钥安全**:Webhook URL 必须存储为 GitHub Secrets,不得硬编码 +- **ESM 兼容性**:所有导入必须使用 `.js` 扩展名(即使源文件是 `.ts`) +- **消息长度**:飞书对消息内容有长度限制,需确保消息内容符合要求 ## External Dependencies -[Document key external services, APIs, or systems] + +### 飞书服务 +- **Webhook URL**:从飞书群聊设置中获取 +- **自定义机器人**:需要在飞书群聊中添加自定义机器人 +- **API 端点**:飞书提供的 Webhook URL + +### GitHub 服务 +- **GitHub Actions**:工作流执行平台 +- **GitHub Secrets**:存储敏感配置信息 +- **组织设置**:配置组织级别密钥 + +### 开发依赖 +- `@types/node` ^22.10.2 - Node.js 类型定义 +- `typescript` ^5.7.2 - TypeScript 编译器 + +## OpenSpec Development Guidelines + +本项目使用 OpenSpec 进行规范驱动的开发。在创建变更提案时,请遵循以下指南: + +### OpenSpec 工作流程 + +1. **创建变更**:当涉及新功能、破坏性变更、架构调整或重大性能/安全改进时创建提案 +2. **实施变更**:按照提案和任务清单逐步实现 +3. **归档变更**:部署后归档变更并更新规范 + +### 关键文档 + +- **[OpenSpec 代理指南](openspec/AGENTS.md)** - 完整的 OpenSpec 工作流程和最佳实践 +- **[提案设计指南](openspec/PROPOSAL_DESIGN_GUIDELINES.md)** - UI 设计和代码流程图的标准格式 + +### 设计文档要求 + +当创建涉及以下内容的变更时,必须包含相应的可视化设计: + +**UI 设计变更**: +- 使用 ASCII 艺术图展示界面原型 +- 使用 Mermaid 时序图描述用户交互流程 +- 标注所有界面状态(正常、悬停、禁用、错误) + +**代码流程变更**: +- 使用 Mermaid 流程图描述数据流 +- 使用 Mermaid 时序图描述 API 交互 +- 使用 Mermaid 架构图描述系统变更 +- 提供完整的代码变更表格(文件路径、变更类型、变更原因、影响范围) + +详细的设计文档格式要求,请参考 `@/openspec/PROPOSAL_DESIGN_GUIDELINES.md`。 + +### 快速命令 + +```bash +openspec list # 列出活跃的变更 +openspec list --specs # 列出所有规范 +openspec show [item] # 显示变更或规范详情 +openspec validate [item] # 验证变更或规范 +openspec archive # 归档已部署的变更 +``` + +## Project Structure + +``` +haginotifier/ +├── .github/ +│ └── workflows/ +│ └── test-notify.yml # 手动测试工作流 +├── src/ +│ ├── feishu.ts # 飞书 Webhook 实现 +│ ├── types.ts # TypeScript 类型定义 +│ └── index.ts # 主模块导出 +├── openspec/ +│ ├── project.md # 项目上下文(本文件) +│ ├── AGENTS.md # OpenSpec 代理指南 +│ ├── PROPOSAL_DESIGN_GUIDELINES.md # 提案设计指南 +│ ├── specs/ # 当前规范(已实现的功能) +│ └── changes/ # 变更提案 +│ └── archive/ # 已归档的变更 +├── action.yml # 复合 action 定义 +├── package.json # 项目配置 +├── tsconfig.json # TypeScript 配置 +├── CLAUDE.md # Claude Code 指令 +├── CHANGELOG.md # 版本变更记录 +└── README.md # 项目文档 +``` + +## 扩展性 + +项目设计支持轻松添加其他通知提供商: + +1. 创建新模块(如 `src/dingtalk.ts`) +2. 实现类似的 `sendNotification` 函数 +3. 在 `src/types.ts` 中添加提供商特定的类型 +4. 从 `src/index.ts` 导出 +5. 更新 `action.yml` 以接受 `provider` 输入参数 + +## 支持的通知提供商 + +当前支持: +- **飞书(Feishu/Lark)** - 完整支持 + +计划支持: +- 钉钉(DingTalk) +- 企业微信(WeCom) +- Slack +- Microsoft Teams + +## 相关资源 + +- [飞书自定义机器人文档](https://open.feishu.cn/document/ukTMukTMukTM/uUTNz4SN1MjL1UzM) +- [GitHub Actions 复合 action](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action) +- [GitHub Secrets 管理](https://docs.github.com/en/actions/security-guides/encrypted-secrets) diff --git a/package-lock.json b/package-lock.json index 5760bd1..9a24847 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,430 @@ "name": "haginotifier", "version": "1.0.0", "license": "MIT", + "dependencies": { + "tsx": "^4.19.2" + }, "devDependencies": { "@types/node": "^22.10.2", "typescript": "^5.7.2" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@types/node": { "version": "22.19.10", "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.10.tgz", @@ -23,6 +442,101 @@ "undici-types": "~6.21.0" } }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", diff --git a/package.json b/package.json index fa23c13..f5ebe0d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "haginotifier", "version": "1.0.0", - "description": "Reusable GitHub Actions workflow for sending notifications to Feishu", + "description": "GitHub Action for sending notifications to Feishu", "type": "module", "keywords": [ "github-actions", @@ -10,6 +10,13 @@ "webhook" ], "license": "MIT", + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "tsx": "^4.19.2" + }, "devDependencies": { "@types/node": "^22.10.2", "typescript": "^5.7.2" diff --git a/src/feishu.ts b/src/feishu.ts index 54240d7..fd7cce3 100644 --- a/src/feishu.ts +++ b/src/feishu.ts @@ -173,12 +173,14 @@ export async function sendNotification( * - FEISHU_MESSAGE: The message content * - FEISHU_MSG_TYPE: Optional message type (default: text) * - FEISHU_TITLE: Optional title for post/interactive messages + * - HAGI_ACTION_MODE: Set to "true" when running as GitHub Action (never exits with error) */ export async function runCli(): Promise { const webhook_url = process.env.FEISHU_WEBHOOK_URL || ""; const message = process.env.FEISHU_MESSAGE || ""; const msg_type = (process.env.FEISHU_MSG_TYPE as "text" | "post" | "interactive") || "text"; const title = process.env.FEISHU_TITLE; + const isActionMode = process.env.HAGI_ACTION_MODE === "true"; const result = await sendNotification({ webhook_url, @@ -189,7 +191,8 @@ export async function runCli(): Promise { console.log(JSON.stringify(result, null, 2)); - if (result.status === "failure") { + // Only exit with error code if NOT in action mode + if (!isActionMode && result.status === "failure") { process.exit(1); } }