reuseable to action#1
Conversation
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughMigrates Feishu notifications from a reusable GitHub Actions workflow to a GitHub Composite Action: deletes the reusable workflow, adds Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 | 🟠 MajorAction mode can still exit non‑zero on unexpected errors.
runCli()is gated, but the top-levelcatchstill callsprocess.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 tonode 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 + fiaction.yml (1)
14-15: Align runtime with the “precompiled dist” design or update docs.Current action executes
src/feishu.tsviatsx, but the design/README emphasize precompileddist/with no runtime TS. Either switch tonode dist/index.jsin the action or revise the docs to reflect the runtime TS dependency.Also applies to: 53-53
| 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 }} | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/test-notify.ymlRepository: 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-actionGitHub 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
-
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
-
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
-
You can’t build the
uses:path fromenv(it must be a static string in workflow YAML), so the workaround is to use a fixed checkout path (likemain) or checkout to the default workspace. (stackoverflow.com)
Citations:
- 1: https://docs.github.com/actions/writing-workflows/choosing-what-your-workflow-does/using-pre-written-building-blocks-in-your-workflow?utm_source=openai
- 2: https://docs.github.com/actions/writing-workflows/choosing-what-your-workflow-does/using-pre-written-building-blocks-in-your-workflow?utm_source=openai
- 3: https://docs.github.com/en/enterprise-server%403.15/actions/learn-github-actions/finding-and-customizing-actions?utm_source=openai
- 4: https://stackoverflow.com/questions/77087749/github-actions-use-local-action-after-checkout-into-different-folder?utm_source=openai
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.
| 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 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "action.yml" -o -name "action.yaml" | head -20Repository: 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 -5Repository: HagiCode-org/haginotifier
Length of output: 84
🏁 Script executed:
cat src/feishu.tsRepository: 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)'}")
EOFRepository: 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'}")
EOFRepository: HagiCode-org/haginotifier
Length of output: 620
🏁 Script executed:
# Check the exact console.log format in feishu.ts
grep -n "console.log" src/feishu.tsRepository: 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)'}'")
EOFRepository: 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_EOFRepository: 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_EOFRepository: 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_EOFRepository: 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.
| 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.
|
|
||
| All notable changes to this project will be documented in this file. | ||
|
|
||
| ## [1.0.0] - 2025-02-09 |
There was a problem hiding this comment.
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.
| ## [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.
| ### 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+ |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and examine the action.yml file
find . -name "action.yml" -o -name "action.yaml" | head -20Repository: 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=20Repository: 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.
|
|
||
| haginotifier 是一个可重用的 GitHub Actions 工作流,用于向飞书(Feishu)发送通知。该项目为组织内所有仓库提供统一的通知机制,消除了在每个仓库中重复实现通知逻辑的需要。通过组织级别的密钥配置,可以实现一次配置、多仓库复用的便捷管理方式。 | ||
|
|
There was a problem hiding this comment.
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.
| - **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 | ||
|
|
There was a problem hiding this comment.
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).
Summary by CodeRabbit
New Features
Tests
Documentation
Chores