ework-aio 0.2.6 → 0.2.7
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/package.json +3 -1
- package/scripts/e2e-install.sh +213 -0
- package/src/cli.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ework-aio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "All-in-one installer for ework (issue tracker) + ework-daemon (AI bridge) + opencode-ework (plugin). One command: npm i -g ework-aio && ework-aio install.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"start": "bun bin/ework-aio.js",
|
|
42
42
|
"check": "tsc --noEmit",
|
|
43
43
|
"test": "bun test",
|
|
44
|
+
"test:e2e": "bash scripts/e2e-install.sh",
|
|
45
|
+
"test:regression": "bash scripts/regression.sh",
|
|
44
46
|
"postinstall": "echo '\\n ework-aio installed. Run \\033[1mework-aio install\\033[0m to set up services.\\n Help: ework-aio --help\\n'"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# End-to-end install test.
|
|
3
|
+
#
|
|
4
|
+
# Spawns a clean debian container (via Dockerfile.regression) and runs the
|
|
5
|
+
# FULL `ework-aio install` flow — not a hand-rolled .env like the older
|
|
6
|
+
# regression.sh. This is the test that would have caught every bug we
|
|
7
|
+
# shipped between v0.2.0 and v0.2.6:
|
|
8
|
+
#
|
|
9
|
+
# - v0.2.4 plugin key fix → caught by issue-create → daemon receives webhook
|
|
10
|
+
# - v0.2.5 daemon binary fix → caught by /api/status probe (was printing help)
|
|
11
|
+
# - v0.2.6 webhook secret → caught by "no invalid signature in daemon.log"
|
|
12
|
+
# - v0.2.6 access log → caught by "no EACCES in web.log"
|
|
13
|
+
#
|
|
14
|
+
# Usage: ./scripts/e2e-install.sh [container-runtime] [npm-tag]
|
|
15
|
+
# container-runtime: docker (default) | podman
|
|
16
|
+
# npm-tag: latest (default) | 0.2.6 | file:.. (for local builds)
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
RUNTIME="${1:-docker}"
|
|
21
|
+
NPM_TAG="${2:-latest}"
|
|
22
|
+
IMAGE="${IMAGE:-ework-aio:e2e}"
|
|
23
|
+
|
|
24
|
+
c_grn=$'\033[32m'; c_red=$'\033[31m'; c_ylw=$'\033[33m'; c_rst=$'\033[0m'
|
|
25
|
+
pass() { printf '%sPASS%s %s\n' "$c_grn" "$c_rst" "$*"; }
|
|
26
|
+
fail() { printf '%sFAIL%s %s\n' "$c_red" "$c_rst" "$*"; exit 1; }
|
|
27
|
+
info() { printf '%s…%s %s\n' "$c_ylw" "$c_rst" "$*" >&2; }
|
|
28
|
+
|
|
29
|
+
if ! "$RUNTIME" image inspect "$IMAGE" >/dev/null 2>&1; then
|
|
30
|
+
info "Building $IMAGE"
|
|
31
|
+
# Build context = repo root (this script's parent dir + ..). Lets us invoke
|
|
32
|
+
# the script from anywhere (npm run test:e2e, CI, etc.) without cwd issues.
|
|
33
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
34
|
+
"$RUNTIME" build --network=host -f "$REPO_ROOT/Dockerfile.regression" \
|
|
35
|
+
-t "$IMAGE" "$REPO_ROOT"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
info "Using image: $IMAGE (testing npm tag: $NPM_TAG)"
|
|
39
|
+
|
|
40
|
+
# Unique ports so we don't collide with a host-side ework stack when run with
|
|
41
|
+
# --network host. Override via env if needed.
|
|
42
|
+
WORK_PORT="${WORK_PORT:-14002}"
|
|
43
|
+
DAEMON_PORT="${DAEMON_PORT:-14101}"
|
|
44
|
+
|
|
45
|
+
# opencode binary is host-specific (path may differ across machines). Mount
|
|
46
|
+
# readonly so the container preflight sees it on PATH. Override via env.
|
|
47
|
+
OPENCODE_HOST_BIN="${OPENCODE_HOST_BIN:-/home/dog/.local/bin/opencode}"
|
|
48
|
+
|
|
49
|
+
"$RUNTIME" run --rm -i --network host \
|
|
50
|
+
-v "$OPENCODE_HOST_BIN:/usr/local/bin/opencode:ro" \
|
|
51
|
+
-e WORK_PORT="$WORK_PORT" \
|
|
52
|
+
-e DAEMON_PORT="$DAEMON_PORT" \
|
|
53
|
+
-e NPM_TAG="$NPM_TAG" \
|
|
54
|
+
-e HTTP_PROXY="${HTTP_PROXY:-}" \
|
|
55
|
+
-e HTTPS_PROXY="${HTTPS_PROXY:-}" \
|
|
56
|
+
-e http_proxy="${HTTP_PROXY:-}" \
|
|
57
|
+
-e https_proxy="${HTTPS_PROXY:-}" \
|
|
58
|
+
-e NO_PROXY="127.0.0.1,localhost" \
|
|
59
|
+
"$IMAGE" bash -euo pipefail <<'EOSCRIPT'
|
|
60
|
+
set -euo pipefail
|
|
61
|
+
|
|
62
|
+
c_grn=$'\033[32m'; c_red=$'\033[31m'; c_ylw=$'\033[33m'; c_rst=$'\033[0m'
|
|
63
|
+
pass() { printf '%sPASS%s %s\n' "$c_grn" "$c_rst" "$*"; }
|
|
64
|
+
fail() { printf '%sFAIL%s %s\n' "$c_red" "$c_rst" "$*"; exit 1; }
|
|
65
|
+
info() { printf '%s…%s %s\n' "$c_ylw" "$c_rst" "$*" >&2; }
|
|
66
|
+
|
|
67
|
+
WORK_PORT="${WORK_PORT:-14002}"
|
|
68
|
+
DAEMON_PORT="${DAEMON_PORT:-14101}"
|
|
69
|
+
NPM_TAG="${NPM_TAG:-latest}"
|
|
70
|
+
DATA_DIR=/tmp/aio-e2e
|
|
71
|
+
|
|
72
|
+
info "clean state"
|
|
73
|
+
rm -rf "$DATA_DIR"
|
|
74
|
+
mkdir -p "$DATA_DIR"
|
|
75
|
+
|
|
76
|
+
info "opencode binary on PATH"
|
|
77
|
+
opencode --version | head -1
|
|
78
|
+
|
|
79
|
+
info "npm install -g ework-web@${NPM_TAG} ework-daemon@latest ework-aio@${NPM_TAG}"
|
|
80
|
+
# Note: ework-web and ework-daemon aren't version-locked to ework-aio. We
|
|
81
|
+
# always install latest of the services and only parameterize ework-aio so
|
|
82
|
+
# we can E2E-test a PR branch against published services.
|
|
83
|
+
for pkg in ework-web ework-daemon opencode-ework; do
|
|
84
|
+
npm install -g "$pkg@latest" 2>&1 | tail -2
|
|
85
|
+
done
|
|
86
|
+
npm install -g "ework-aio@${NPM_TAG}" 2>&1 | tail -2
|
|
87
|
+
|
|
88
|
+
info "v0.2.5 regression: all 4 bins on PATH"
|
|
89
|
+
for b in ework-aio ework-web ework-daemon ework-daemon-server; do
|
|
90
|
+
command -v "$b" >/dev/null 2>&1 || fail "$b not on PATH"
|
|
91
|
+
pass "$b -> $(command -v "$b")"
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
info "run ework-aio install (the real installer — no hand-rolled .env)"
|
|
95
|
+
# --allow-root: container runs as root; refusing would test an unrealistic
|
|
96
|
+
# path. The installer's data still lands in /tmp/aio-e2e via --data-dir.
|
|
97
|
+
ework-aio install \
|
|
98
|
+
--allow-root \
|
|
99
|
+
--data-dir "$DATA_DIR" \
|
|
100
|
+
--port "$WORK_PORT" \
|
|
101
|
+
--daemon-port "$DAEMON_PORT" \
|
|
102
|
+
--bot-name e2e-bot \
|
|
103
|
+
--yes 2>&1 | tee /tmp/install.log
|
|
104
|
+
|
|
105
|
+
pass "install exited 0"
|
|
106
|
+
|
|
107
|
+
info "v0.2.6 regression: webhook secrets match across web and daemon .env"
|
|
108
|
+
WEB_SEC=$(grep ^WORK_DAEMON_WEBHOOK_SECRET= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
109
|
+
DAE_SEC=$(grep ^GITEA_WEBHOOK_SECRET= "$DATA_DIR/ework-daemon/.env" | cut -d= -f2-)
|
|
110
|
+
[[ -n "$WEB_SEC" ]] || fail "WORK_DAEMON_WEBHOOK_SECRET empty in web .env"
|
|
111
|
+
[[ -n "$DAE_SEC" ]] || fail "GITEA_WEBHOOK_SECRET empty in daemon .env"
|
|
112
|
+
[[ "$WEB_SEC" == "$DAE_SEC" ]] \
|
|
113
|
+
|| fail "webhook secrets differ (web=$WEB_SEC daemon=$DAE_SEC)"
|
|
114
|
+
pass "secrets match (${#WEB_SEC} chars)"
|
|
115
|
+
|
|
116
|
+
info "v0.2.6 regression: WORK_ACCESS_LOG set, not defaulting to /tmp/ework-access.log"
|
|
117
|
+
ACCESS_LOG_VAL=$(grep ^WORK_ACCESS_LOG= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
118
|
+
[[ -n "$ACCESS_LOG_VAL" ]] || fail "WORK_ACCESS_LOG missing from web .env"
|
|
119
|
+
[[ "$ACCESS_LOG_VAL" != "/tmp/ework-access.log" ]] \
|
|
120
|
+
|| fail "WORK_ACCESS_LOG still defaults to /tmp/ework-access.log"
|
|
121
|
+
pass "WORK_ACCESS_LOG=$ACCESS_LOG_VAL"
|
|
122
|
+
|
|
123
|
+
info "wait for ework-web /login"
|
|
124
|
+
for i in $(seq 1 60); do
|
|
125
|
+
if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
|
|
126
|
+
pass "/login responds (after ${i} half-seconds)"
|
|
127
|
+
break
|
|
128
|
+
fi
|
|
129
|
+
sleep 0.5
|
|
130
|
+
[[ $i -eq 60 ]] && { tail -30 "$DATA_DIR/run/web.log" 2>/dev/null; fail "ework-web did not come up"; }
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
info "wait for daemon /api/status"
|
|
134
|
+
for i in $(seq 1 60); do
|
|
135
|
+
if curl -sf -o /dev/null "http://127.0.0.1:$DAEMON_PORT/api/status"; then
|
|
136
|
+
pass "/api/status responds (after ${i} half-seconds)"
|
|
137
|
+
break
|
|
138
|
+
fi
|
|
139
|
+
sleep 0.5
|
|
140
|
+
[[ $i -eq 60 ]] && { tail -30 "$DATA_DIR/run/daemon.log" 2>/dev/null; fail "daemon did not come up"; }
|
|
141
|
+
done
|
|
142
|
+
|
|
143
|
+
info "v0.2.6 regression: no EACCES in web.log"
|
|
144
|
+
sleep 1
|
|
145
|
+
if grep -q "EACCES" "$DATA_DIR/run/web.log" 2>/dev/null; then
|
|
146
|
+
fail "EACCES in web.log:"
|
|
147
|
+
grep EACCES "$DATA_DIR/run/web.log" | head -3
|
|
148
|
+
fi
|
|
149
|
+
pass "no EACCES in web.log"
|
|
150
|
+
|
|
151
|
+
info "v0.2.6 regression: no invalid signature in daemon.log (pre-issue-create)"
|
|
152
|
+
if grep -q "invalid signature" "$DATA_DIR/run/daemon.log" 2>/dev/null; then
|
|
153
|
+
fail "invalid signature BEFORE issue create — autoWire webhook must have fired"
|
|
154
|
+
grep "invalid signature" "$DATA_DIR/run/daemon.log" | head -3
|
|
155
|
+
fi
|
|
156
|
+
pass "no invalid signature yet"
|
|
157
|
+
|
|
158
|
+
info "build admin auth cookie"
|
|
159
|
+
WORK_TOKEN=$(grep ^WORK_TOKEN= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
160
|
+
WORK_COOKIE_SECRET=$(grep ^WORK_COOKIE_SECRET= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
161
|
+
COOKIE_SIG=$(printf '%s' "$WORK_TOKEN" \
|
|
162
|
+
| openssl dgst -sha256 -hmac "$WORK_COOKIE_SECRET" -binary \
|
|
163
|
+
| base64 | tr '+/' '-_' | tr -d '=')
|
|
164
|
+
AUTH_COOKIE="ework_auth=${WORK_TOKEN}.${COOKIE_SIG}"
|
|
165
|
+
|
|
166
|
+
info "create test project (triggers autoWire webhook registration)"
|
|
167
|
+
PROJ_CODE=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
|
|
168
|
+
"http://127.0.0.1:$WORK_PORT/projects" \
|
|
169
|
+
-H "Cookie: $AUTH_COOKIE" \
|
|
170
|
+
--data-urlencode 'owner=e2e' \
|
|
171
|
+
--data-urlencode 'name=test')
|
|
172
|
+
[[ "$PROJ_CODE" == "303" || "$PROJ_CODE" == "302" ]] \
|
|
173
|
+
|| fail "project create failed: HTTP $PROJ_CODE"
|
|
174
|
+
pass "project create -> $PROJ_CODE"
|
|
175
|
+
|
|
176
|
+
info "create issue (triggers webhook POST to daemon)"
|
|
177
|
+
ISSUE_HEAD=$(curl -sS -i -X POST \
|
|
178
|
+
"http://127.0.0.1:$WORK_PORT/e2e/test/issues" \
|
|
179
|
+
-H "Cookie: $AUTH_COOKIE" \
|
|
180
|
+
--data-urlencode 'title=e2e test' \
|
|
181
|
+
--data-urlencode 'body=trigger webhook')
|
|
182
|
+
ISSUE_STATUS=$(printf '%s\n' "$ISSUE_HEAD" | head -1 | awk '{print $2}')
|
|
183
|
+
[[ "$ISSUE_STATUS" == "303" ]] \
|
|
184
|
+
|| fail "issue create did not return 303: $ISSUE_STATUS"
|
|
185
|
+
pass "issue create -> 303"
|
|
186
|
+
|
|
187
|
+
info "wait for webhook delivery + daemon processing"
|
|
188
|
+
sleep 5
|
|
189
|
+
|
|
190
|
+
info "v0.2.6 regression: webhook was accepted (no invalid signature after issue create)"
|
|
191
|
+
if grep -q "invalid signature" "$DATA_DIR/run/daemon.log" 2>/dev/null; then
|
|
192
|
+
fail "invalid signature AFTER issue create — webhook secret mismatch"
|
|
193
|
+
grep "invalid signature" "$DATA_DIR/run/daemon.log" | head -5
|
|
194
|
+
fi
|
|
195
|
+
pass "no invalid signature after issue create"
|
|
196
|
+
|
|
197
|
+
info "daemon received the issue event"
|
|
198
|
+
ACTIVE=$(curl -sS "http://127.0.0.1:$DAEMON_PORT/api/status" | jq -r '.issues // 0')
|
|
199
|
+
[[ "$ACTIVE" -ge 1 ]] \
|
|
200
|
+
|| fail "daemon did not register the issue (issues=$ACTIVE)"
|
|
201
|
+
pass "daemon registered issue (issues=$ACTIVE)"
|
|
202
|
+
|
|
203
|
+
echo
|
|
204
|
+
echo "===== E2E PASSED ====="
|
|
205
|
+
echo " ework-aio version: $(ework-aio --version 2>/dev/null || echo unknown)"
|
|
206
|
+
echo " data dir: $DATA_DIR"
|
|
207
|
+
echo " web: http://127.0.0.1:$WORK_PORT/login"
|
|
208
|
+
echo " daemon: http://127.0.0.1:$DAEMON_PORT/api/status"
|
|
209
|
+
echo " logs: $DATA_DIR/run/{web,daemon}.log"
|
|
210
|
+
EOSCRIPT
|
|
211
|
+
|
|
212
|
+
echo
|
|
213
|
+
echo "${c_grn}E2E COMPLETE${c_rst}"
|