Skip to content

reuseable to action#1

Merged
newbe36524 merged 1 commit intomainfrom
pr
Feb 9, 2026
Merged

reuseable to action#1
newbe36524 merged 1 commit intomainfrom
pr

Conversation

@newbe36524
Copy link
Copy Markdown
Contributor

@newbe36524 newbe36524 commented Feb 9, 2026

Summary by CodeRabbit

  • New Features

    • Replaced reusable workflow with a published composite GitHub Action for Feishu notifications.
    • Added an action-mode toggle to change CLI failure behavior during runs.
  • Tests

    • Added a PR validation workflow to run build, type checks, and validation steps.
  • Documentation

    • Updated README, CHANGELOG, migration guide, and design docs for the new action model.
  • Chores

    • Introduced build scripts and adjusted runtime dependencies.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 9, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Migrates Feishu notifications from a reusable GitHub Actions workflow to a GitHub Composite Action: deletes the reusable workflow, adds action.yml composite action, updates CI/workflows to call the action inline, adds build scripts/packaging, updates code to support "action mode", and expands docs/specs and validation workflows.

Changes

Cohort / File(s) Summary
Workflows
\.github/workflows/notify.yml, \.github/workflows/pr-validation.yml, \.github/workflows/test-notify.yml
Removed legacy reusable workflow; added pr-validation.yml; updated test-notify.yml to call the local composite action (uses: ./) and read outputs from step outputs instead of job needs outputs.
Action Definition
action.yml
New composite action metadata defining inputs (message, msg_type, title), outputs (status, timestamp, response), Node.js setup, install step, and run step that executes compiled/tsx code and emits outputs.
Source & Runtime
src/feishu.ts, src/..., package.json
Added HAGI_ACTION_MODE env flag to avoid process.exit(1) in action mode; added build and prepublishOnly scripts; added tsx dependency; adjusted description to "GitHub Action".
Docs & Changelog
README.md, CHANGELOG.md
Extensive documentation updates: migration guide, new usage examples for composite action (uses: ...@v1), environment variable usage, versioning guidance, breaking-change notes, and new changelog entries for v1.0.0.
Design & Specs
openspec/changes/.../design.md, .../proposal.md, .../spec.md, .../tasks.md
Added design, proposal, formal spec, and task list documenting migration rationale, architecture, build/release plan, inputs/outputs contract, and validation/testing checklist.
Project Docs
openspec/project.md
Expanded project documentation with concrete tech stack, conventions, architecture patterns, testing strategy, release and extensibility guidance for notification providers.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as "PR Workflow"
  participant Action as "Composite Action\n(action.yml)"
  participant Node as "Node runtime / dist"
  participant Feishu as "Feishu API"
  participant WorkflowStep as "Caller Step (outputs)"

  Workflow->>Action: invoke (uses: ./, with inputs, env FEISHU_WEBHOOK_URL)
  Action->>Node: setup Node, install deps, run script
  Node->>Feishu: POST webhook (message, type, title)
  Feishu-->>Node: response (status, body, timestamp)
  Node-->>Action: write outputs (status,timestamp,response) to GITHUB_OUTPUT
  Action-->>WorkflowStep: step.outputs.status / timestamp / response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 I hopped from workflows to action bright,

Steps called me now instead of night.
I pass my message, title, type,
No crashing exits — gentle stripe.
Docs and build in tidy line, hooray — Feishu, send and shine! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'reuseable to action' is vague and contains a spelling error ('reuseable' should be 'reusable'). It lacks clarity about what specific change is being made, making it difficult for reviewers scanning history to understand the primary change. Revise the title to be more specific and clear, such as 'Convert reusable workflow to GitHub composite action' or 'Migrate Feishu notification from workflow to action', while correcting the spelling.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr

Comment @coderabbitai help to get the list of available commands and usage tips.

@newbe36524 newbe36524 merged commit ac70ab5 into main Feb 9, 2026
1 check passed
@newbe36524 newbe36524 deleted the pr branch February 9, 2026 12:12
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/feishu.ts (1)

176-205: ⚠️ Potential issue | 🟠 Major

Action mode can still exit non‑zero on unexpected errors.
runCli() is gated, but the top-level catch still calls process.exit(1), so action mode can still fail the workflow on thrown errors.

🔧 Suggested fix
 if (import.meta.url === `file://${process.argv[1]}`) {
   runCli().catch((error) => {
     console.error("CLI error:", error);
-    process.exit(1);
+    if (process.env.HAGI_ACTION_MODE !== "true") {
+      process.exit(1);
+    }
   });
 }
🤖 Fix all issues with AI agents
In @.github/workflows/test-notify.yml:
- Around line 32-41: The workflow's local-action step (id: notification, uses:
./) will fail without checking out the repository; add a preceding step that
runs actions/checkout@v4 (or latest) before the notification step so the
workspace contains action.yml and other files referenced by the local action;
ensure the checkout step appears above the step with id "notification" and
retains any required permissions/checkout settings for the action to run.

In `@action.yml`:
- Around line 53-59: The current grep/cut/sed extraction from OUTPUT (from
running feishu.ts) fails on pretty-printed JSON and multiline/escaped responses;
replace that fragile parsing by piping OUTPUT into a JSON parser (jq) to extract
.status, .timestamp and .response (use -r/raw output) instead of the grep/cut
pipeline, and write status and timestamp to GITHUB_OUTPUT normally while writing
RESPONSE using the GitHub Actions multiline output format (here-doc style) so
multiline/quoted response values are preserved; update the variables STATUS,
TIMESTAMP and RESPONSE and remove the grep/cut/sed logic, keeping the npx tsx
src/feishu.ts invocation that produces OUTPUT.

In `@CHANGELOG.md`:
- Line 5: Update the release date in the CHANGELOG entry for version header "##
[1.0.0] - 2025-02-09" to the correct 2026-02-09 (or the PR timestamp) so the
changelog matches the PR date; locate the "## [1.0.0] - 2025-02-09" line and
replace the year 2025 with 2026.

In
`@openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/design.md`:
- Around line 14-18: The design doc claims no runtime tsx but the action
currently invokes "tsx src/feishu.ts"; either update the design text to
accurately state the action runs with tsx or change the action to execute
precompiled JS from "dist/" (e.g., add a build step compiling TypeScript to dist
and replace "tsx src/feishu.ts" with running the compiled entry like "node
dist/feishu.js"); update mentions of "tsx" and "dist/" throughout the doc
(including the referenced sections) to keep doc and action consistent.

In
`@openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/proposal.md`:
- Around line 16-20: The proposal claims "Remove runtime dependency on `tsx`"
but the composite action still runs `npx tsx src/feishu.ts` and `tsx` remains as
a dependency; fix this by either updating the proposal text to state that the
action currently runs TypeScript via `npx tsx` at runtime (reference `npx tsx
src/feishu.ts` and the composite action in `action.yml`) OR implement the
planned precompiled flow: add a TypeScript build step to produce `dist/index.js`
from `src/` (update build scripts/CI to compile), change the composite action
entry to run `node dist/index.js` instead of `npx tsx ...`, and remove `tsx`
from runtime dependencies (package.json) so the documentation and implementation
match.

In
`@openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/specs/feishu-notification/spec.md`:
- Around line 68-82: The action currently runs TypeScript at runtime via the
"npx tsx src/feishu.ts" invocation in action.yml, which violates the spec that
requires pre-compiled dist/index.js; change action.yml to execute "node
dist/index.js" (or equivalent) and ensure the CI/release pipeline includes a
build step that runs the TypeScript compilation (e.g., npm run build or tsc) to
produce dist/index.js before packaging/releasing so the action runs on Node.js
18+ without tsx at runtime.

In `@openspec/project.md`:
- Around line 4-6: The Purpose section still describes a reusable workflow;
update its text to state that haginotifier is a composite GitHub Action
(composite action) that provides a reusable Feishu notification action for
repos, describe its role as an action (not a workflow), and adjust wording to
mention action inputs/outputs and organization-level key configuration for
multi-repo reuse; locate and edit the "Purpose" (or opening paragraph) in
project.md to replace "可重用的 GitHub Actions 工作流" and related phrases with "复合
GitHub Action (composite action)" and corresponding action-oriented language.

In `@README.md`:
- Around line 9-14: The README claim "Pre-compiled JavaScript with no runtime
TypeScript compilation overhead" is inaccurate because the GitHub Action runs
tsx (tsx src/feishu.ts); either update the README wording to reflect that the
action executes TypeScript at runtime via tsx, or change the action to run
compiled JS (e.g., build to dist and run node dist/feishu.js). Locate references
to "pre-compiled JavaScript" in README and either replace with "runs via tsx
(runtime TypeScript execution)" or adjust the workflow to build the project and
run node against the output (ensure the entry module names: src/feishu.ts and
resulting dist/feishu.js are updated accordingly).
🧹 Nitpick comments (2)
.github/workflows/pr-validation.yml (1)

26-57: Make validation resilient to tsx vs dist execution.
These steps hardcode tsx. If the runtime is switched to node dist/index.js, PR validation will start failing.

♻️ Suggested refactor
       - 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
+          if grep -q 'node dist/index.js' action.yml; then
+            ACTION_CMD="node dist/index.js"
+          else
+            ACTION_CMD="npx tsx src/feishu.ts"
+          fi
+          FEISHU_MESSAGE="Test message" HAGI_ACTION_MODE="true" $ACTION_CMD
@@
       - 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
+          # Verify action.yml references a supported execution command
+          if ! grep -Eq 'npx tsx src/feishu\.ts|node dist/index\.js' action.yml; then
+            echo "Error: action.yml doesn't specify a supported execution command"
+            exit 1
+          fi
action.yml (1)

14-15: Align runtime with the “precompiled dist” design or update docs.

Current action executes src/feishu.ts via tsx, but the design/README emphasize precompiled dist/ with no runtime TS. Either switch to node dist/index.js in the action or revise the docs to reflect the runtime TS dependency.

Also applies to: 53-53

Comment on lines 32 to +41
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 }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/test-notify.yml

Repository: HagiCode-org/haginotifier

Length of output: 1864


🌐 Web query:

GitHub Actions local action checkout requirement uses ./ action.yml

💡 Result:

When you reference a local action like:

- uses: ./.github/actions/my-action

GitHub Actions looks for action.yml/action.yaml (or a Dockerfile) on disk under $GITHUB_WORKSPACE at that relative path. If the repository files aren’t present there yet, you’ll get the “Can’t find 'action.yml'… Did you forget to run actions/checkout” error. (docs.github.com)

What this means in practice

  1. Yes—local actions require the repo contents to be available, so you typically need a checkout step first: (docs.github.com)

    - uses: actions/checkout@v5
    - uses: ./.github/actions/my-action
  2. If you checkout into a non-default folder using with: path: ..., then your local action reference must include that folder (still relative to $GITHUB_WORKSPACE): (docs.github.com)

    - uses: actions/checkout@v5
      with:
        path: main
    - uses: ./main/.github/actions/my-action
  3. You can’t build the uses: path from env (it must be a static string in workflow YAML), so the workaround is to use a fixed checkout path (like main) or checkout to the default workspace. (stackoverflow.com)

Citations:


Local action requires repository checkout.

uses: ./ needs the repo contents in the workspace; without actions/checkout, this step will fail to locate action.yml.

✅ Add checkout before using the local action
     steps:
+      - uses: actions/checkout@v4
       - id: notification
         uses: ./ # Uses action.yml from the current repository
🤖 Prompt for AI Agents
In @.github/workflows/test-notify.yml around lines 32 - 41, The workflow's
local-action step (id: notification, uses: ./) will fail without checking out
the repository; add a preceding step that runs actions/checkout@v4 (or latest)
before the notification step so the workspace contains action.yml and other
files referenced by the local action; ensure the checkout step appears above the
step with id "notification" and retains any required permissions/checkout
settings for the action to run.

Comment thread action.yml
Comment on lines +53 to +59
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "action.yml" -o -name "action.yaml" | head -20

Repository: HagiCode-org/haginotifier

Length of output: 81


🏁 Script executed:

cat -n action.yml | sed -n '50,65p'

Repository: HagiCode-org/haginotifier

Length of output: 677


🏁 Script executed:

find . -name "feishu.ts" | head -5

Repository: HagiCode-org/haginotifier

Length of output: 84


🏁 Script executed:

cat src/feishu.ts

Repository: HagiCode-org/haginotifier

Length of output: 5315


🏁 Script executed:

# Let's verify the JSON output format by checking what pretty-printed JSON looks like
# and test the current grep patterns against it

python3 << 'EOF'
import json

# Simulate the output from feishu.ts with pretty-printing
result = {
    "status": "success",
    "timestamp": "2024-01-15T10:30:00Z",
    "response": "Notification sent successfully"
}

output = json.dumps(result, indent=2)
print("=== Pretty-printed JSON output from feishu.ts ===")
print(output)
print("\n=== Testing grep pattern against this output ===")

import subprocess

# Test the current grep pattern
grep_pattern = '"status":"[^"]*"'
result = subprocess.run(['grep', '-o', grep_pattern], 
                       input=output, text=True, capture_output=True)
print(f"Pattern: {grep_pattern}")
print(f"Result: {result.stdout if result.stdout else '(NO MATCH)'}")

# Test with multiline response
result_multiline = {
    "status": "success",
    "timestamp": "2024-01-15T10:30:00Z",
    "response": "Line 1\nLine 2\nLine 3"
}
output_multiline = json.dumps(result_multiline, indent=2)
print("\n=== Testing with multiline response ===")
print(output_multiline)
print("\nTesting grep pattern on multiline response:")
result = subprocess.run(['grep', '-o', '"response":"[^"]*"'], 
                       input=output_multiline, text=True, capture_output=True)
print(f"Result: {result.stdout if result.stdout else '(NO MATCH)'}")
EOF

Repository: HagiCode-org/haginotifier

Length of output: 536


🏁 Script executed:

# Let's check what console.log(JSON.stringify(...)) actually outputs
# The code uses console.log(JSON.stringify(result, null, 2))
# which means pretty-printed with 2-space indent

python3 << 'EOF'
import json
import subprocess

# Test with actual feishu.ts output format
result = {
    "status": "success",
    "timestamp": "2024-01-15T10:30:00Z",
    "response": "Notification sent successfully"
}

# This is what feishu.ts actually outputs (with indent=2)
output = json.dumps(result, indent=2)

print("=== Actual output from: console.log(JSON.stringify(result, null, 2)) ===")
print(output)
print("\n=== Current grep patterns will NOT match ===")

# Test the patterns from action.yml
patterns = [
    '"status":"[^"]*"',
    '"timestamp":"[^"]*"',
    '"response":"[^"]*"'
]

for pattern in patterns:
    result_grep = subprocess.run(['grep', '-o', pattern], 
                               input=output, text=True, capture_output=True)
    print(f"Pattern '{pattern}': {'MATCH' if result_grep.stdout else 'NO MATCH'}")

print("\n=== Testing with escaped quotes in response ===")
result_escaped = {
    "status": "success",
    "timestamp": "2024-01-15T10:30:00Z",
    "response": 'Message with "quotes" inside'
}
output_escaped = json.dumps(result_escaped, indent=2)
print(output_escaped)

result_grep = subprocess.run(['grep', '-o', '"response":"[^"]*"'], 
                           input=output_escaped, text=True, capture_output=True)
print(f"\nGrep pattern on escaped quotes: {'MATCH' if result_grep.stdout else 'NO MATCH'}")
EOF

Repository: HagiCode-org/haginotifier

Length of output: 620


🏁 Script executed:

# Check the exact console.log format in feishu.ts
grep -n "console.log" src/feishu.ts

Repository: HagiCode-org/haginotifier

Length of output: 120


🏁 Script executed:

# Let's verify the actual behavior by testing what the action.yml script would do
# with the real output format from feishu.ts

python3 << 'EOF'
import subprocess
import json

# Simulate the actual output from feishu.ts
result = {
    "status": "success",
    "timestamp": "2024-01-15T10:30:00Z",
    "response": "Notification sent successfully"
}

# This is what console.log(JSON.stringify(result, null, 2)) outputs
output = json.dumps(result, indent=2)

print("=== Actual output from feishu.ts (with indent=2) ===")
print(output)
print("\n=== Running current action.yml bash patterns ===\n")

# Test STATUS extraction
bash_cmd = '''echo "$OUTPUT" | grep -o '"status":"[^"]*"' | cut -d'"' -f4'''
result = subprocess.run(['bash', '-c', bash_cmd], 
                       input=output, text=True, capture_output=True, env={'OUTPUT': output})
print(f"STATUS result: '{result.stdout.strip() if result.stdout else '(empty)'}'")

# Test with compact JSON (no indent)
compact_output = json.dumps(result, separators=(',', ':'))
print(f"\n=== With compact JSON (no spaces) ===")
print(compact_output)

bash_cmd = '''echo "$OUTPUT" | grep -o '"status":"[^"]*"' | cut -d'"' -f4'''
result = subprocess.run(['bash', '-c', bash_cmd], 
                       input=compact_output, text=True, capture_output=True, env={'OUTPUT': compact_output})
print(f"STATUS result: '{result.stdout.strip() if result.stdout else '(empty)'}'")
EOF

Repository: HagiCode-org/haginotifier

Length of output: 998


🏁 Script executed:

# Let's test the bash patterns directly without Python complications

bash << 'BASH_EOF'
# Simulate the actual output from feishu.ts (with indent=2)
OUTPUT=$(cat <<'JSON'
{
  "status": "success",
  "timestamp": "2024-01-15T10:30:00Z",
  "response": "Notification sent successfully"
}
JSON
)

echo "=== Pretty-printed JSON output from feishu.ts ==="
echo "$OUTPUT"
echo ""
echo "=== Testing current action.yml grep patterns ==="

echo "Pattern: grep -o '\"status\":\"[^\"]*\"'"
echo "$OUTPUT" | grep -o '"status":"[^"]*"'
echo "Result: $?"
echo ""

echo "Pattern: grep -o '\"response\":\"[^\"]*\"'"
echo "$OUTPUT" | grep -o '"response":"[^"]*"'
echo "Result: $?"
echo ""

# Now test with a response containing newlines
OUTPUT2=$(cat <<'JSON'
{
  "status": "success",
  "timestamp": "2024-01-15T10:30:00Z",
  "response": "Line 1\nLine 2\nLine 3"
}
JSON
)

echo "=== With escaped newlines in response ==="
echo "$OUTPUT2"
echo ""
echo "Pattern: grep -o '\"response\":\"[^\"]*\"'"
echo "$OUTPUT2" | grep -o '"response":"[^"]*"'
echo "Result: $?"
echo ""

# Test with escaped quotes
OUTPUT3=$(cat <<'JSON'
{
  "status": "success",
  "timestamp": "2024-01-15T10:30:00Z",
  "response": "Message with \"quotes\" inside"
}
JSON
)

echo "=== With escaped quotes in response ==="
echo "$OUTPUT3"
echo ""
echo "Pattern: grep -o '\"response\":\"[^\"]*\"'"
echo "$OUTPUT3" | grep -o '"response":"[^"]*"' | cut -d'"' -f4
echo "Result: $?"

BASH_EOF

Repository: HagiCode-org/haginotifier

Length of output: 776


🏁 Script executed:

# Test if the proposed solution would work with the actual pretty-printed JSON

bash << 'BASH_EOF'
OUTPUT=$(cat <<'JSON'
{
  "status": "success",
  "timestamp": "2024-01-15T10:30:00Z",
  "response": "Notification sent successfully"
}
JSON
)

echo "=== Testing proposed Node.js JSON parsing solution ==="
echo "Original OUTPUT:"
echo "$OUTPUT"
echo ""

echo "=== Extracting status with Node.js ==="
STATUS=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.status??'')")
echo "STATUS='$STATUS'"
echo ""

echo "=== Extracting timestamp with Node.js ==="
TIMESTAMP=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.timestamp??'')")
echo "TIMESTAMP='$TIMESTAMP'"
echo ""

echo "=== Extracting response with Node.js ==="
RESPONSE=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.response??'')")
echo "RESPONSE='$RESPONSE'"
echo ""

# Test with response containing newlines
OUTPUT2=$(cat <<'JSON'
{
  "status": "success",
  "timestamp": "2024-01-15T10:30:00Z",
  "response": "Line 1\nLine 2\nLine 3"
}
JSON
)

echo "=== Testing with multiline response ==="
echo "Original OUTPUT:"
echo "$OUTPUT2"
echo ""

RESPONSE2=$(printf '%s' "$OUTPUT2" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.response??'')")
echo "RESPONSE='$RESPONSE2'"
echo ""

# Test with escaped quotes
OUTPUT3=$(cat <<'JSON'
{
  "status": "success",
  "timestamp": "2024-01-15T10:30:00Z",
  "response": "Message with \"quotes\" inside"
}
JSON
)

echo "=== Testing with escaped quotes ==="
RESPONSE3=$(printf '%s' "$OUTPUT3" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.response??'')")
echo "RESPONSE='$RESPONSE3'"

BASH_EOF

Repository: HagiCode-org/haginotifier

Length of output: 743


🏁 Script executed:

# Let's test the multiline output format that was proposed

bash << 'BASH_EOF'
# Create a temp file to simulate GITHUB_OUTPUT
export GITHUB_OUTPUT="/tmp/test_github_output"
rm -f "$GITHUB_OUTPUT"
touch "$GITHUB_OUTPUT"

RESPONSE="Line 1
Line 2
Line 3"

echo "=== Testing current format (single line) ==="
echo "response=$RESPONSE" >> "$GITHUB_OUTPUT"
cat "$GITHUB_OUTPUT"
echo ""

# Clear for next test
rm -f "$GITHUB_OUTPUT"
touch "$GITHUB_OUTPUT"

echo "=== Testing proposed multiline format (EOF delimiter) ==="
{
  echo "response<<EOF"
  echo "$RESPONSE"
  echo "EOF"
} >> "$GITHUB_OUTPUT"
cat "$GITHUB_OUTPUT"
echo ""

# Show the difference in how they would be parsed
echo "=== Single line format issues ==="
SINGLE_LINE=$(grep "^response=" "$GITHUB_OUTPUT" 2>/dev/null || echo "not found in multiline test")
echo "Value: $SINGLE_LINE"

BASH_EOF

Repository: HagiCode-org/haginotifier

Length of output: 311


Fix JSON parsing - current implementation is broken and will fail with the actual output format.

The current grep/cut patterns fail to match the JSON output from feishu.ts because console.log(JSON.stringify(result, null, 2)) produces pretty-printed JSON with spaces and newlines (e.g., "status": "value"), while the patterns expect compact JSON. Additionally, responses containing newlines or escaped quotes will cause corruption. Use proper JSON parsing instead, and apply the multiline output format for responses that may contain newlines.

✅ Safer parsing + multiline output
-        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
+        OUTPUT=$(npx --no-install tsx src/feishu.ts)
+        STATUS=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.status??'')")
+        TIMESTAMP=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.timestamp??'')")
+        RESPONSE=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.response??'')")
+        echo "status=$STATUS" >> "$GITHUB_OUTPUT"
+        echo "timestamp=$TIMESTAMP" >> "$GITHUB_OUTPUT"
+        {
+          echo "response<<EOF"
+          echo "$RESPONSE"
+          echo "EOF"
+        } >> "$GITHUB_OUTPUT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
OUTPUT=$(npx --no-install tsx src/feishu.ts)
STATUS=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.status??'')")
TIMESTAMP=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.timestamp??'')")
RESPONSE=$(printf '%s' "$OUTPUT" | node -e "const fs=require('fs');const o=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(o.response??'')")
echo "status=$STATUS" >> "$GITHUB_OUTPUT"
echo "timestamp=$TIMESTAMP" >> "$GITHUB_OUTPUT"
{
echo "response<<EOF"
echo "$RESPONSE"
echo "EOF"
} >> "$GITHUB_OUTPUT"
🤖 Prompt for AI Agents
In `@action.yml` around lines 53 - 59, The current grep/cut/sed extraction from
OUTPUT (from running feishu.ts) fails on pretty-printed JSON and
multiline/escaped responses; replace that fragile parsing by piping OUTPUT into
a JSON parser (jq) to extract .status, .timestamp and .response (use -r/raw
output) instead of the grep/cut pipeline, and write status and timestamp to
GITHUB_OUTPUT normally while writing RESPONSE using the GitHub Actions multiline
output format (here-doc style) so multiline/quoted response values are
preserved; update the variables STATUS, TIMESTAMP and RESPONSE and remove the
grep/cut/sed logic, keeping the npx tsx src/feishu.ts invocation that produces
OUTPUT.

Comment thread CHANGELOG.md

All notable changes to this project will be documented in this file.

## [1.0.0] - 2025-02-09
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Changelog date appears one year behind.
Given this PR’s timestamp (2026‑02‑09), the release date likely needs updating.

📅 Suggested fix
-## [1.0.0] - 2025-02-09
+## [1.0.0] - 2026-02-09
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## [1.0.0] - 2025-02-09
## [1.0.0] - 2026-02-09
🤖 Prompt for AI Agents
In `@CHANGELOG.md` at line 5, Update the release date in the CHANGELOG entry for
version header "## [1.0.0] - 2025-02-09" to the correct 2026-02-09 (or the PR
timestamp) so the changelog matches the PR date; locate the "## [1.0.0] -
2025-02-09" line and replace the year 2025 with 2026.

Comment on lines +68 to +82
### 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+
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find and examine the action.yml file
find . -name "action.yml" -o -name "action.yaml" | head -20

Repository: HagiCode-org/haginotifier

Length of output: 81


🏁 Script executed:

# Check the spec file mentioned in the review
cat "openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/specs/feishu-notification/spec.md"

Repository: HagiCode-org/haginotifier

Length of output: 3488


🏁 Script executed:

# Search for tsx references in the codebase to understand runtime requirements
rg -n "tsx|dist/index" --max-count=20

Repository: HagiCode-org/haginotifier

Length of output: 3959


Implementation uses tsx at runtime; spec mandates pre-compiled dist/index.js.
The action.yml (line 53) executes npx tsx src/feishu.ts, requiring the tsx dependency at runtime. This directly contradicts the spec requirement (lines 68–82) which mandates pre-compiled dist/index.js without runtime TypeScript compilation. Either complete the compilation pipeline to use dist/index.js as specified, or update the spec to reflect the current tsx-based runtime.

🤖 Prompt for AI Agents
In
`@openspec/changes/archive/2026-02-09-convert-reusable-workflow-to-github-action/specs/feishu-notification/spec.md`
around lines 68 - 82, The action currently runs TypeScript at runtime via the
"npx tsx src/feishu.ts" invocation in action.yml, which violates the spec that
requires pre-compiled dist/index.js; change action.yml to execute "node
dist/index.js" (or equivalent) and ensure the CI/release pipeline includes a
build step that runs the TypeScript compilation (e.g., npm run build or tsc) to
produce dist/index.js before packaging/releasing so the action runs on Node.js
18+ without tsx at runtime.

Comment thread openspec/project.md
Comment on lines +4 to 6

haginotifier 是一个可重用的 GitHub Actions 工作流,用于向飞书(Feishu)发送通知。该项目为组织内所有仓库提供统一的通知机制,消除了在每个仓库中重复实现通知逻辑的需要。通过组织级别的密钥配置,可以实现一次配置、多仓库复用的便捷管理方式。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update purpose: this is now a composite action, not a reusable workflow.

The project was migrated to a composite action in this PR, but the Purpose section still describes a reusable workflow.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: 数量词修饰并列短语,可能产生歧义
Context: ...机制,消除了在每个仓库中重复实现通知逻辑的需要。通过组织级别的密钥配置,可以实现一次配置、多仓库复用的便捷管理方式。 ## Tech Stack - **TypeSc...

(s5)

🤖 Prompt for AI Agents
In `@openspec/project.md` around lines 4 - 6, The Purpose section still describes
a reusable workflow; update its text to state that haginotifier is a composite
GitHub Action (composite action) that provides a reusable Feishu notification
action for repos, describe its role as an action (not a workflow), and adjust
wording to mention action inputs/outputs and organization-level key
configuration for multi-repo reuse; locate and edit the "Purpose" (or opening
paragraph) in project.md to replace "可重用的 GitHub Actions 工作流" and related
phrases with "复合 GitHub Action (composite action)" and corresponding
action-oriented language.

Comment thread README.md
Comment on lines +9 to 14
- **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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

README overstates “pre-compiled JS” while action runs tsx.

The action currently runs tsx src/feishu.ts, so “no runtime TypeScript compilation” is inaccurate unless you switch the action to node dist/.... Please update README or action accordingly.

🤖 Prompt for AI Agents
In `@README.md` around lines 9 - 14, The README claim "Pre-compiled JavaScript
with no runtime TypeScript compilation overhead" is inaccurate because the
GitHub Action runs tsx (tsx src/feishu.ts); either update the README wording to
reflect that the action executes TypeScript at runtime via tsx, or change the
action to run compiled JS (e.g., build to dist and run node dist/feishu.js).
Locate references to "pre-compiled JavaScript" in README and either replace with
"runs via tsx (runtime TypeScript execution)" or adjust the workflow to build
the project and run node against the output (ensure the entry module names:
src/feishu.ts and resulting dist/feishu.js are updated accordingly).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant