@zenithbuild/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +15 -0
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +25 -0
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +34 -0
- package/.github/pull_request_template.md +15 -0
- package/.github/workflows/discord-changelog.yml +141 -0
- package/.github/workflows/discord-notify.yml +242 -0
- package/.github/workflows/discord-version.yml +195 -0
- package/.prettierignore +13 -0
- package/.prettierrc +21 -0
- package/.zen.d.ts +15 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/app/components/Button.zen +46 -0
- package/app/components/Link.zen +11 -0
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +59 -0
- package/app/pages/about.zen +23 -0
- package/app/pages/blog/[id].zen +53 -0
- package/app/pages/blog/index.zen +32 -0
- package/app/pages/dynamic-dx.zen +712 -0
- package/app/pages/dynamic-primitives.zen +453 -0
- package/app/pages/index.zen +154 -0
- package/app/pages/navigation-demo.zen +229 -0
- package/app/pages/posts/[...slug].zen +61 -0
- package/app/pages/primitives-demo.zen +273 -0
- package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
- package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
- package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +601 -0
- package/assets/logos/README.md +54 -0
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +39 -0
- package/compiler/README.md +380 -0
- package/compiler/errors/compilerError.ts +24 -0
- package/compiler/finalize/finalizeOutput.ts +163 -0
- package/compiler/finalize/generateFinalBundle.ts +82 -0
- package/compiler/index.ts +44 -0
- package/compiler/ir/types.ts +83 -0
- package/compiler/legacy/binding.ts +254 -0
- package/compiler/legacy/bindings.ts +338 -0
- package/compiler/legacy/component-process.ts +1208 -0
- package/compiler/legacy/component.ts +301 -0
- package/compiler/legacy/event.ts +50 -0
- package/compiler/legacy/expression.ts +1149 -0
- package/compiler/legacy/mutation.ts +280 -0
- package/compiler/legacy/parse.ts +299 -0
- package/compiler/legacy/split.ts +608 -0
- package/compiler/legacy/types.ts +32 -0
- package/compiler/output/types.ts +34 -0
- package/compiler/parse/detectMapExpressions.ts +102 -0
- package/compiler/parse/parseScript.ts +22 -0
- package/compiler/parse/parseTemplate.ts +425 -0
- package/compiler/parse/parseZenFile.ts +66 -0
- package/compiler/parse/trackLoopContext.ts +82 -0
- package/compiler/runtime/dataExposure.ts +291 -0
- package/compiler/runtime/generateDOM.ts +144 -0
- package/compiler/runtime/generateHydrationBundle.ts +383 -0
- package/compiler/runtime/hydration.ts +309 -0
- package/compiler/runtime/navigation.ts +432 -0
- package/compiler/runtime/thinRuntime.ts +160 -0
- package/compiler/runtime/transformIR.ts +256 -0
- package/compiler/runtime/wrapExpression.ts +84 -0
- package/compiler/runtime/wrapExpressionWithLoop.ts +77 -0
- package/compiler/spa-build.ts +1000 -0
- package/compiler/test/validate-test.ts +104 -0
- package/compiler/transform/generateBindings.ts +47 -0
- package/compiler/transform/generateHTML.ts +28 -0
- package/compiler/transform/transformNode.ts +126 -0
- package/compiler/transform/transformTemplate.ts +38 -0
- package/compiler/validate/validateExpressions.ts +168 -0
- package/core/index.ts +135 -0
- package/core/lifecycle/index.ts +49 -0
- package/core/lifecycle/zen-mount.ts +182 -0
- package/core/lifecycle/zen-unmount.ts +88 -0
- package/core/reactivity/index.ts +54 -0
- package/core/reactivity/tracking.ts +167 -0
- package/core/reactivity/zen-batch.ts +57 -0
- package/core/reactivity/zen-effect.ts +139 -0
- package/core/reactivity/zen-memo.ts +146 -0
- package/core/reactivity/zen-ref.ts +52 -0
- package/core/reactivity/zen-signal.ts +121 -0
- package/core/reactivity/zen-state.ts +180 -0
- package/core/reactivity/zen-untrack.ts +44 -0
- package/docs/COMMENTS.md +111 -0
- package/docs/COMMITS.md +36 -0
- package/docs/CONTRIBUTING.md +116 -0
- package/docs/STYLEGUIDE.md +62 -0
- package/package.json +44 -0
- package/router/index.ts +76 -0
- package/router/manifest.ts +314 -0
- package/router/navigation/ZenLink.zen +231 -0
- package/router/navigation/index.ts +78 -0
- package/router/navigation/zen-link.ts +584 -0
- package/router/runtime.ts +458 -0
- package/router/types.ts +168 -0
- package/runtime/build.ts +17 -0
- package/runtime/serve.ts +93 -0
- package/scripts/webhook-proxy.ts +213 -0
- package/tsconfig.json +28 -0
package/.eslintignore
ADDED
package/.gitattributes
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Compiler errors for invalid state declarations and usage
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
Add compiler enforcement for the following invalid scenarios:
|
|
5
|
+
- Use of undeclared state variables: `{{ unknown }}`.
|
|
6
|
+
- Use of expressions in placeholders: `{{ count + 1 }}`.
|
|
7
|
+
- Invalid state initialization: `state count = count + 1;`.
|
|
8
|
+
- State mutations outside allowed event handlers.
|
|
9
|
+
|
|
10
|
+
## Acceptance Criteria
|
|
11
|
+
|
|
12
|
+
1. Compiler throws errors for undeclared state usage in placeholders.
|
|
13
|
+
2. Compiler disallows expressions within placeholders.
|
|
14
|
+
3. Errors are thrown for invalid state initialization logic.
|
|
15
|
+
4. State mutations are only allowed inside event handlers.
|
|
16
|
+
5. Test cases ensure all invalid scenarios are caught at compile time.
|
|
17
|
+
|
|
18
|
+
### Example:
|
|
19
|
+
```html
|
|
20
|
+
<script>
|
|
21
|
+
state count = 5;
|
|
22
|
+
state other = count + 1; // Error
|
|
23
|
+
</script>
|
|
24
|
+
<p>{{ count + 1 }}</p> <!-- Error -->
|
|
25
|
+
<p>{{ unknown }}</p> <!-- Error -->
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: "\U0001F4A1 New Issue"
|
|
2
|
+
description: Suggest a new task
|
|
3
|
+
|
|
4
|
+
title: 'nn-your-issue'
|
|
5
|
+
|
|
6
|
+
body:
|
|
7
|
+
- type: markdown
|
|
8
|
+
attributes:
|
|
9
|
+
value: |
|
|
10
|
+
_Thank you for taking the time to propose a new idea_
|
|
11
|
+
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: problem
|
|
14
|
+
attributes:
|
|
15
|
+
label: '<h2>Describe the Problem</h2>'
|
|
16
|
+
description: Please provide a short user story **DESCRIPTION** of the **PROBLEM**
|
|
17
|
+
placeholder: As a user I...
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: solution
|
|
23
|
+
attributes:
|
|
24
|
+
label: '<h2>Acceptance Criteria</h2>'
|
|
25
|
+
description: Please provide the REQUIREMENTS that must be fulfilled to consider this issue resolved.
|
|
26
|
+
placeholder: X must show Y when Z...
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
|
|
30
|
+
- type: textarea
|
|
31
|
+
id: references
|
|
32
|
+
attributes:
|
|
33
|
+
label: '<h2>References</h2>'
|
|
34
|
+
description: Please provide any **SCREENSHOTS**, **CODE SNIPPETS** or **LINKS** to the codebase or documentation which will help implement a solution.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ๐ Summary
|
|
2
|
+
|
|
3
|
+
This merge implements/resolves... (include relevant ticket)
|
|
4
|
+
|
|
5
|
+
## ๐ How can we Reproduce/Test?
|
|
6
|
+
|
|
7
|
+
Please provide instructions so we can reproduce:
|
|
8
|
+
|
|
9
|
+
- [ ] Step 1
|
|
10
|
+
- [ ] Step 2
|
|
11
|
+
- [ ] ...
|
|
12
|
+
|
|
13
|
+
## ๐ธ Any screenshots or links to points in your code or references elsewhere as needed
|
|
14
|
+
|
|
15
|
+
Please delete if not relevant.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
name: Discord Changelog Notifications
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
types: [closed]
|
|
9
|
+
|
|
10
|
+
# Security: Limit permissions to only what's needed
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pull-requests: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
notify:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- name: Send Changelog to Discord
|
|
20
|
+
env:
|
|
21
|
+
DISCORD_CHANGELOG_WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}
|
|
22
|
+
run: |
|
|
23
|
+
set -e # Exit on error
|
|
24
|
+
set +x # Don't echo commands (prevents secret exposure in logs)
|
|
25
|
+
|
|
26
|
+
# Mask the webhook URL in logs to prevent accidental exposure
|
|
27
|
+
echo "::add-mask::$DISCORD_CHANGELOG_WEBHOOK_URL"
|
|
28
|
+
|
|
29
|
+
# Validate secret is set
|
|
30
|
+
if [ -z "$DISCORD_CHANGELOG_WEBHOOK_URL" ]; then
|
|
31
|
+
echo "โ DISCORD_CHANGELOG_WEBHOOK_URL secret is not set"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Validate webhook URL format for security
|
|
36
|
+
if [[ ! "$DISCORD_CHANGELOG_WEBHOOK_URL" =~ ^https://discord\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+$ ]]; then
|
|
37
|
+
echo "โ Invalid Discord webhook URL format"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Determine event type and get data
|
|
42
|
+
if [ "${{ github.event_name }}" = "push" ]; then
|
|
43
|
+
EVENT_TYPE="push"
|
|
44
|
+
BRANCH="${{ github.ref_name }}"
|
|
45
|
+
PUSHER="${{ github.event.pusher.name }}"
|
|
46
|
+
COMPARE_URL="${{ github.event.compare }}"
|
|
47
|
+
|
|
48
|
+
# Get commit messages and count
|
|
49
|
+
COMMITS_JSON='${{ toJSON(github.event.commits) }}'
|
|
50
|
+
COMMITS=$(echo "$COMMITS_JSON" | jq -r '.[] | "- " + (.message | split("\n")[0])' | head -10)
|
|
51
|
+
COMMITS_COUNT=$(echo "$COMMITS_JSON" | jq 'length')
|
|
52
|
+
|
|
53
|
+
COLOR="3447003" # Blue
|
|
54
|
+
EMOJI="๐ฆ"
|
|
55
|
+
TITLE="Code Pushed"
|
|
56
|
+
# Escape backticks for shell variable
|
|
57
|
+
BRANCH_ESC=$(echo "$BRANCH" | sed "s/\\\`/\\\\\\\`/g")
|
|
58
|
+
DESCRIPTION="**$COMMITS_COUNT commit(s)** pushed to branch \`$BRANCH_ESC\` by $PUSHER"
|
|
59
|
+
|
|
60
|
+
elif [ "${{ github.event.pull_request.merged }}" = "true" ]; then
|
|
61
|
+
EVENT_TYPE="merge"
|
|
62
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
63
|
+
PR_TITLE="${{ github.event.pull_request.title }}"
|
|
64
|
+
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
|
65
|
+
PR_URL="${{ github.event.pull_request.html_url }}"
|
|
66
|
+
BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
|
|
67
|
+
HEAD_BRANCH="${{ github.event.pull_request.head.ref }}"
|
|
68
|
+
|
|
69
|
+
COLOR="3066993" # Green
|
|
70
|
+
EMOJI="๐"
|
|
71
|
+
TITLE="Pull Request Merged"
|
|
72
|
+
# Escape backticks and newlines
|
|
73
|
+
HEAD_BRANCH_ESC=$(echo "$HEAD_BRANCH" | sed "s/\\\`/\\\\\\\`/g")
|
|
74
|
+
BASE_BRANCH_ESC=$(echo "$BASE_BRANCH" | sed "s/\\\`/\\\\\\\`/g")
|
|
75
|
+
PR_TITLE_ESC=$(echo "$PR_TITLE" | sed "s/\\\`/\\\\\\\`/g" | sed 's/$/\\n/' | tr -d '\n' | sed 's/\\n$//')
|
|
76
|
+
DESCRIPTION="**PR #$PR_NUMBER**: $PR_TITLE_ESC\nMerged \`$HEAD_BRANCH_ESC\` โ \`$BASE_BRANCH_ESC\` by $PR_AUTHOR"
|
|
77
|
+
COMPARE_URL="$PR_URL"
|
|
78
|
+
COMMITS=""
|
|
79
|
+
else
|
|
80
|
+
echo "โญ๏ธ PR not merged, skipping..."
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Export variables for Node.js script
|
|
85
|
+
export EMOJI TITLE COLOR
|
|
86
|
+
|
|
87
|
+
# Use Node.js to properly build JSON payload with escaping
|
|
88
|
+
cat > /tmp/discord_changelog_payload.js <<JS_EOF
|
|
89
|
+
const repo = "${{ github.repository }}";
|
|
90
|
+
const emoji = process.env.EMOJI || '๐ฆ';
|
|
91
|
+
const title = process.env.TITLE || 'Code Updated';
|
|
92
|
+
const color = parseInt(process.env.COLOR || '3447003', 10);
|
|
93
|
+
const description = '$DESCRIPTION';
|
|
94
|
+
const compareUrl = '$COMPARE_URL';
|
|
95
|
+
const commits = '$COMMITS';
|
|
96
|
+
|
|
97
|
+
// Build embed
|
|
98
|
+
const embed = {
|
|
99
|
+
title: emoji + ' ' + title,
|
|
100
|
+
description: description,
|
|
101
|
+
url: compareUrl || 'https://github.com/' + repo,
|
|
102
|
+
color: color,
|
|
103
|
+
fields: commits && commits.length > 0 ? [
|
|
104
|
+
{
|
|
105
|
+
name: 'Recent Commits',
|
|
106
|
+
value: commits.length > 1000 ? commits.substring(0, 1000) + '...' : commits,
|
|
107
|
+
inline: false
|
|
108
|
+
}
|
|
109
|
+
] : [],
|
|
110
|
+
footer: {
|
|
111
|
+
text: repo
|
|
112
|
+
},
|
|
113
|
+
timestamp: new Date().toISOString()
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
console.log(JSON.stringify({ embeds: [embed] }));
|
|
117
|
+
JS_EOF
|
|
118
|
+
|
|
119
|
+
# Run the Node.js script
|
|
120
|
+
PAYLOAD=$(node /tmp/discord_changelog_payload.js)
|
|
121
|
+
|
|
122
|
+
# Send to Discord with error handling
|
|
123
|
+
HTTP_CODE=$(curl -s -o /tmp/discord_response.txt -w "%{http_code}" \
|
|
124
|
+
-X POST "$DISCORD_CHANGELOG_WEBHOOK_URL" \
|
|
125
|
+
-H "Content-Type: application/json" \
|
|
126
|
+
-d "$PAYLOAD")
|
|
127
|
+
|
|
128
|
+
# Check response
|
|
129
|
+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
130
|
+
echo "โ
Successfully sent changelog notification to Discord"
|
|
131
|
+
else
|
|
132
|
+
echo "โ Failed to send notification (HTTP $HTTP_CODE)"
|
|
133
|
+
if [ -f /tmp/discord_response.txt ]; then
|
|
134
|
+
echo "Response: $(cat /tmp/discord_response.txt)"
|
|
135
|
+
fi
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Clean up
|
|
140
|
+
rm -f /tmp/discord_response.txt /tmp/discord_changelog_payload.js
|
|
141
|
+
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
name: Discord Issue Notifications
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [opened, closed, reopened, edited, assigned, unassigned, labeled, unlabeled]
|
|
6
|
+
issue_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
|
|
9
|
+
# Security: Limit permissions to only what's needed
|
|
10
|
+
permissions:
|
|
11
|
+
issues: write # Needed to add labels to track notified issues
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
notify:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- name: Send to Discord
|
|
19
|
+
env:
|
|
20
|
+
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
|
21
|
+
run: |
|
|
22
|
+
set -e # Exit on error
|
|
23
|
+
set +x # Don't echo commands (prevents secret exposure in logs)
|
|
24
|
+
|
|
25
|
+
# Mask the webhook URL in logs to prevent accidental exposure
|
|
26
|
+
echo "::add-mask::$DISCORD_WEBHOOK_URL"
|
|
27
|
+
|
|
28
|
+
# Validate secret is set
|
|
29
|
+
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
|
30
|
+
echo "โ DISCORD_WEBHOOK_URL secret is not set"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Validate webhook URL format for security
|
|
35
|
+
if [[ ! "$DISCORD_WEBHOOK_URL" =~ ^https://discord\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+$ ]]; then
|
|
36
|
+
echo "โ Invalid Discord webhook URL format"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Get issue number and check if already notified
|
|
41
|
+
ISSUE_NUMBER="${{ github.event.issue.number }}"
|
|
42
|
+
REPO="${{ github.repository }}"
|
|
43
|
+
TRACKING_LABEL="discord-notified"
|
|
44
|
+
ACTION="${{ github.event.action }}"
|
|
45
|
+
|
|
46
|
+
# Check if issue already has the tracking label (already notified)
|
|
47
|
+
HAS_LABEL=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json labels --jq ".labels[] | select(.name == \"$TRACKING_LABEL\") | .name" 2>/dev/null || echo "")
|
|
48
|
+
|
|
49
|
+
# Prevent duplicates based on event type
|
|
50
|
+
case "$ACTION" in
|
|
51
|
+
opened)
|
|
52
|
+
# For new issues, skip if already notified
|
|
53
|
+
if [ -n "$HAS_LABEL" ]; then
|
|
54
|
+
echo "โญ๏ธ Issue #$ISSUE_NUMBER already notified to Discord, skipping..."
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
;;
|
|
58
|
+
edited)
|
|
59
|
+
# For edits: if not notified yet, treat as initial notification (for backfilling)
|
|
60
|
+
# If already notified, send update notification
|
|
61
|
+
if [ -z "$HAS_LABEL" ]; then
|
|
62
|
+
echo "โน๏ธ Issue #$ISSUE_NUMBER hasn't been initially notified yet, treating edit as initial notification..."
|
|
63
|
+
# Change action to "opened" for initial notification formatting
|
|
64
|
+
ACTION="opened"
|
|
65
|
+
fi
|
|
66
|
+
;;
|
|
67
|
+
labeled|unlabeled)
|
|
68
|
+
# For label changes: if not notified yet, treat as initial notification (for backfilling)
|
|
69
|
+
# If already notified, send update notification
|
|
70
|
+
if [ -z "$HAS_LABEL" ]; then
|
|
71
|
+
echo "โน๏ธ Issue #$ISSUE_NUMBER hasn't been initially notified yet, treating label change as initial notification..."
|
|
72
|
+
# Change action to "opened" for initial notification formatting
|
|
73
|
+
ACTION="opened"
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
76
|
+
assigned|unassigned)
|
|
77
|
+
# For assignment changes, only send if issue was already notified (initial notification sent)
|
|
78
|
+
if [ -z "$HAS_LABEL" ]; then
|
|
79
|
+
echo "โญ๏ธ Issue #$ISSUE_NUMBER hasn't been initially notified yet, skipping assignment notification..."
|
|
80
|
+
exit 0
|
|
81
|
+
fi
|
|
82
|
+
;;
|
|
83
|
+
closed|reopened)
|
|
84
|
+
# Always send for state changes (closed/reopened) - these are important
|
|
85
|
+
# But skip if issue was never initially notified
|
|
86
|
+
if [ -z "$HAS_LABEL" ]; then
|
|
87
|
+
echo "โญ๏ธ Issue #$ISSUE_NUMBER hasn't been initially notified yet, skipping state change notification..."
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
|
|
93
|
+
# Determine color, emoji, and title based on action (ACTION already set above)
|
|
94
|
+
case "$ACTION" in
|
|
95
|
+
opened)
|
|
96
|
+
COLOR="3066993" # Green
|
|
97
|
+
EMOJI="๐"
|
|
98
|
+
TITLE="New Issue Created"
|
|
99
|
+
;;
|
|
100
|
+
closed)
|
|
101
|
+
COLOR="15158332" # Red
|
|
102
|
+
EMOJI="โ
"
|
|
103
|
+
TITLE="Issue Closed"
|
|
104
|
+
;;
|
|
105
|
+
reopened)
|
|
106
|
+
COLOR="15105570" # Orange
|
|
107
|
+
EMOJI="๐"
|
|
108
|
+
TITLE="Issue Reopened"
|
|
109
|
+
;;
|
|
110
|
+
edited)
|
|
111
|
+
COLOR="3447003" # Blue
|
|
112
|
+
EMOJI="โ๏ธ"
|
|
113
|
+
TITLE="Issue Edited"
|
|
114
|
+
;;
|
|
115
|
+
assigned|unassigned)
|
|
116
|
+
COLOR="10181046" # Purple
|
|
117
|
+
EMOJI="๐ค"
|
|
118
|
+
TITLE="Issue Assignment Changed"
|
|
119
|
+
;;
|
|
120
|
+
labeled|unlabeled)
|
|
121
|
+
COLOR="15844367" # Gold
|
|
122
|
+
EMOJI="๐ท๏ธ"
|
|
123
|
+
TITLE="Issue Label Changed"
|
|
124
|
+
;;
|
|
125
|
+
*)
|
|
126
|
+
COLOR="3447003" # Blue
|
|
127
|
+
EMOJI="๐"
|
|
128
|
+
TITLE="Issue Updated"
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
|
|
132
|
+
# Export variables for Node.js script
|
|
133
|
+
export EMOJI TITLE COLOR
|
|
134
|
+
|
|
135
|
+
# Use Node.js to properly build JSON payload with escaping
|
|
136
|
+
# Write Node.js script to temp file (no quotes on heredoc to allow GitHub Actions expansion)
|
|
137
|
+
cat > /tmp/discord_payload.js <<JS_EOF
|
|
138
|
+
const issue = ${{ toJSON(github.event.issue) }};
|
|
139
|
+
const repo = "${{ github.repository }}";
|
|
140
|
+
const emoji = process.env.EMOJI || '๐';
|
|
141
|
+
const title = process.env.TITLE || 'Issue Updated';
|
|
142
|
+
const color = parseInt(process.env.COLOR || '3447003', 10);
|
|
143
|
+
|
|
144
|
+
// Format labels - use string concatenation to avoid template literal issues
|
|
145
|
+
const labels = issue.labels && issue.labels.length > 0
|
|
146
|
+
? issue.labels.map(function(l) { return String.fromCharCode(96) + l.name + String.fromCharCode(96); }).join(' ')
|
|
147
|
+
: '*No labels*';
|
|
148
|
+
|
|
149
|
+
// Truncate body
|
|
150
|
+
let body = issue.body || '*No description*';
|
|
151
|
+
if (body.length > 1000) {
|
|
152
|
+
body = body.substring(0, 1000) + '...';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Build embed
|
|
156
|
+
const embed = {
|
|
157
|
+
title: emoji + ' ' + title,
|
|
158
|
+
description: '**' + issue.title + '**',
|
|
159
|
+
url: issue.html_url,
|
|
160
|
+
color: color,
|
|
161
|
+
fields: [
|
|
162
|
+
{
|
|
163
|
+
name: 'Issue #',
|
|
164
|
+
value: '#' + issue.number,
|
|
165
|
+
inline: true
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'State',
|
|
169
|
+
value: issue.state,
|
|
170
|
+
inline: true
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'Labels',
|
|
174
|
+
value: labels,
|
|
175
|
+
inline: false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'Description',
|
|
179
|
+
value: body,
|
|
180
|
+
inline: false
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
author: {
|
|
184
|
+
name: issue.user.login,
|
|
185
|
+
icon_url: issue.user.avatar_url
|
|
186
|
+
},
|
|
187
|
+
footer: {
|
|
188
|
+
text: repo
|
|
189
|
+
},
|
|
190
|
+
timestamp: new Date().toISOString()
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
console.log(JSON.stringify({ embeds: [embed] }));
|
|
194
|
+
JS_EOF
|
|
195
|
+
|
|
196
|
+
# Run the Node.js script
|
|
197
|
+
PAYLOAD=$(node /tmp/discord_payload.js)
|
|
198
|
+
|
|
199
|
+
# Send to Discord with error handling
|
|
200
|
+
HTTP_CODE=$(curl -s -o /tmp/discord_response.txt -w "%{http_code}" \
|
|
201
|
+
-X POST "$DISCORD_WEBHOOK_URL" \
|
|
202
|
+
-H "Content-Type: application/json" \
|
|
203
|
+
-d "$PAYLOAD")
|
|
204
|
+
|
|
205
|
+
# Check response
|
|
206
|
+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
207
|
+
echo "โ
Successfully sent notification to Discord"
|
|
208
|
+
|
|
209
|
+
# Mark issue as notified by adding label (for "opened" events or initial "edited" notifications)
|
|
210
|
+
# This prevents duplicate initial notifications
|
|
211
|
+
if [ "$ACTION" = "opened" ] || [ -z "$HAS_LABEL" ]; then
|
|
212
|
+
# Check if label exists, create if not
|
|
213
|
+
LABEL_EXISTS=$(gh label list --repo "$REPO" --json name --jq ".[] | select(.name == \"$TRACKING_LABEL\") | .name" 2>/dev/null || echo "")
|
|
214
|
+
|
|
215
|
+
if [ -z "$LABEL_EXISTS" ]; then
|
|
216
|
+
echo "Creating tracking label: $TRACKING_LABEL"
|
|
217
|
+
gh label create "$TRACKING_LABEL" \
|
|
218
|
+
--repo "$REPO" \
|
|
219
|
+
--color "0E8A16" \
|
|
220
|
+
--description "Issue has been notified to Discord" \
|
|
221
|
+
2>/dev/null || true
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Add label to issue to mark as notified
|
|
225
|
+
echo "Marking issue #$ISSUE_NUMBER as notified"
|
|
226
|
+
gh issue edit "$ISSUE_NUMBER" \
|
|
227
|
+
--repo "$REPO" \
|
|
228
|
+
--add-label "$TRACKING_LABEL" \
|
|
229
|
+
2>/dev/null || echo "โ ๏ธ Could not add label (may already exist)"
|
|
230
|
+
fi
|
|
231
|
+
else
|
|
232
|
+
echo "โ Failed to send notification (HTTP $HTTP_CODE)"
|
|
233
|
+
# Log error response without exposing webhook URL
|
|
234
|
+
if [ -f /tmp/discord_response.txt ]; then
|
|
235
|
+
echo "Response: $(cat /tmp/discord_response.txt)"
|
|
236
|
+
fi
|
|
237
|
+
exit 1
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
# Clean up
|
|
241
|
+
rm -f /tmp/discord_response.txt /tmp/discord_payload.js
|
|
242
|
+
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
name: Discord Release Notifications
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published, edited, prereleased, released]
|
|
6
|
+
|
|
7
|
+
# Security: Limit permissions to only what's needed
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
notify:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Send Release Notification to Discord
|
|
16
|
+
env:
|
|
17
|
+
DISCORD_VERSION_WEBHOOK_URL: ${{ secrets.DISCORD_VERSION_WEBHOOK_URL }}
|
|
18
|
+
run: |
|
|
19
|
+
set -e # Exit on error
|
|
20
|
+
set +x # Don't echo commands (prevents secret exposure in logs)
|
|
21
|
+
|
|
22
|
+
# Mask the webhook URL in logs to prevent accidental exposure
|
|
23
|
+
echo "::add-mask::$DISCORD_VERSION_WEBHOOK_URL"
|
|
24
|
+
|
|
25
|
+
# Validate secret is set
|
|
26
|
+
if [ -z "$DISCORD_VERSION_WEBHOOK_URL" ]; then
|
|
27
|
+
echo "โ DISCORD_VERSION_WEBHOOK_URL secret is not set"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Validate webhook URL format for security
|
|
32
|
+
if [[ ! "$DISCORD_VERSION_WEBHOOK_URL" =~ ^https://discord\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+$ ]]; then
|
|
33
|
+
echo "โ Invalid Discord webhook URL format"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Get release information
|
|
38
|
+
RELEASE_TAG="${{ github.event.release.tag_name }}"
|
|
39
|
+
RELEASE_NAME="${{ github.event.release.name }}"
|
|
40
|
+
RELEASE_BODY="${{ github.event.release.body }}"
|
|
41
|
+
RELEASE_URL="${{ github.event.release.html_url }}"
|
|
42
|
+
RELEASE_AUTHOR="${{ github.event.release.author.login }}"
|
|
43
|
+
RELEASE_PRERELEASE="${{ github.event.release.prerelease }}"
|
|
44
|
+
RELEASE_ACTION="${{ github.event.action }}"
|
|
45
|
+
|
|
46
|
+
REPO="${{ github.repository }}"
|
|
47
|
+
|
|
48
|
+
# Determine release type and formatting
|
|
49
|
+
if [ "$RELEASE_PRERELEASE" = "true" ]; then
|
|
50
|
+
COLOR="15105570" # Orange
|
|
51
|
+
EMOJI="๐งช"
|
|
52
|
+
TITLE="Pre-Release Published"
|
|
53
|
+
else
|
|
54
|
+
# Parse version to determine type
|
|
55
|
+
VERSION_TYPE=$(node <<VERSION_SCRIPT
|
|
56
|
+
const tag = '$RELEASE_TAG';
|
|
57
|
+
// Remove 'v' prefix if present
|
|
58
|
+
const version = tag.replace(/^v/i, '');
|
|
59
|
+
const parts = version.split('.').map(function(p) { return parseInt(p) || 0; });
|
|
60
|
+
|
|
61
|
+
if (parts[0] > 0 && parts[1] === 0 && parts[2] === 0) {
|
|
62
|
+
console.log('major');
|
|
63
|
+
} else if (parts[1] > 0 && parts[2] === 0) {
|
|
64
|
+
console.log('minor');
|
|
65
|
+
} else {
|
|
66
|
+
console.log('patch');
|
|
67
|
+
}
|
|
68
|
+
VERSION_SCRIPT
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
case "$VERSION_TYPE" in
|
|
72
|
+
major)
|
|
73
|
+
COLOR="15158332" # Red
|
|
74
|
+
EMOJI="๐"
|
|
75
|
+
TITLE="Major Release"
|
|
76
|
+
;;
|
|
77
|
+
minor)
|
|
78
|
+
COLOR="3066993" # Green
|
|
79
|
+
EMOJI="โจ"
|
|
80
|
+
TITLE="Minor Release"
|
|
81
|
+
;;
|
|
82
|
+
patch)
|
|
83
|
+
COLOR="3447003" # Blue
|
|
84
|
+
EMOJI="๐ง"
|
|
85
|
+
TITLE="Patch Release"
|
|
86
|
+
;;
|
|
87
|
+
*)
|
|
88
|
+
COLOR="3447003" # Blue
|
|
89
|
+
EMOJI="๐ฆ"
|
|
90
|
+
TITLE="Release Published"
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Handle different release actions
|
|
96
|
+
case "$RELEASE_ACTION" in
|
|
97
|
+
published)
|
|
98
|
+
# Already set above
|
|
99
|
+
;;
|
|
100
|
+
edited)
|
|
101
|
+
COLOR="15844367" # Gold
|
|
102
|
+
EMOJI="โ๏ธ"
|
|
103
|
+
TITLE="Release Edited"
|
|
104
|
+
;;
|
|
105
|
+
prereleased)
|
|
106
|
+
COLOR="15105570" # Orange
|
|
107
|
+
EMOJI="๐งช"
|
|
108
|
+
TITLE="Pre-Release Published"
|
|
109
|
+
;;
|
|
110
|
+
released)
|
|
111
|
+
# Use version-based formatting
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
|
|
115
|
+
# Export variables for Node.js script
|
|
116
|
+
export EMOJI TITLE COLOR
|
|
117
|
+
|
|
118
|
+
# Use Node.js to properly build JSON payload with escaping
|
|
119
|
+
cat > /tmp/discord_version_payload.js <<JS_EOF
|
|
120
|
+
const repo = "$REPO";
|
|
121
|
+
const emoji = process.env.EMOJI || '๐ฆ';
|
|
122
|
+
const title = process.env.TITLE || 'Release Published';
|
|
123
|
+
const color = parseInt(process.env.COLOR || '3447003', 10);
|
|
124
|
+
const releaseTag = '$RELEASE_TAG';
|
|
125
|
+
const releaseName = '$RELEASE_NAME';
|
|
126
|
+
const releaseBody = '$RELEASE_BODY';
|
|
127
|
+
const releaseUrl = '$RELEASE_URL';
|
|
128
|
+
const releaseAuthor = '$RELEASE_AUTHOR';
|
|
129
|
+
const isPrerelease = '$RELEASE_PRERELEASE' === 'true';
|
|
130
|
+
|
|
131
|
+
// Truncate release body if too long
|
|
132
|
+
let body = releaseBody || '*No release notes*';
|
|
133
|
+
if (body.length > 1000) {
|
|
134
|
+
body = body.substring(0, 1000) + '...';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Build embed
|
|
138
|
+
const embed = {
|
|
139
|
+
title: emoji + ' ' + title,
|
|
140
|
+
description: '**' + (releaseName || releaseTag) + '**' + (isPrerelease ? ' (Pre-release)' : ''),
|
|
141
|
+
url: releaseUrl,
|
|
142
|
+
color: color,
|
|
143
|
+
fields: [
|
|
144
|
+
{
|
|
145
|
+
name: 'Tag',
|
|
146
|
+
value: String.fromCharCode(96) + releaseTag + String.fromCharCode(96),
|
|
147
|
+
inline: true
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'Type',
|
|
151
|
+
value: isPrerelease ? 'Pre-release' : 'Release',
|
|
152
|
+
inline: true
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
footer: {
|
|
156
|
+
text: repo + ' โข ' + releaseAuthor
|
|
157
|
+
},
|
|
158
|
+
timestamp: new Date().toISOString()
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Add release notes if available
|
|
162
|
+
if (body && body !== '*No release notes*') {
|
|
163
|
+
embed.fields.push({
|
|
164
|
+
name: 'Release Notes',
|
|
165
|
+
value: body,
|
|
166
|
+
inline: false
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(JSON.stringify({ embeds: [embed] }));
|
|
171
|
+
JS_EOF
|
|
172
|
+
|
|
173
|
+
# Run the Node.js script
|
|
174
|
+
PAYLOAD=$(node /tmp/discord_version_payload.js)
|
|
175
|
+
|
|
176
|
+
# Send to Discord with error handling
|
|
177
|
+
HTTP_CODE=$(curl -s -o /tmp/discord_response.txt -w "%{http_code}" \
|
|
178
|
+
-X POST "$DISCORD_VERSION_WEBHOOK_URL" \
|
|
179
|
+
-H "Content-Type: application/json" \
|
|
180
|
+
-d "$PAYLOAD")
|
|
181
|
+
|
|
182
|
+
# Check response
|
|
183
|
+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
184
|
+
echo "โ
Successfully sent version update notification to Discord"
|
|
185
|
+
else
|
|
186
|
+
echo "โ Failed to send notification (HTTP $HTTP_CODE)"
|
|
187
|
+
if [ -f /tmp/discord_response.txt ]; then
|
|
188
|
+
echo "Response: $(cat /tmp/discord_response.txt)"
|
|
189
|
+
fi
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Clean up
|
|
194
|
+
rm -f /tmp/discord_response.txt /tmp/discord_version_payload.js
|
|
195
|
+
|
package/.prettierignore
ADDED