ework-aio 0.3.8 → 0.3.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-aio",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
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",
@@ -46,8 +46,8 @@
46
46
  "postinstall": "echo '\\n ework-aio installed. Run \\033[1mework-aio install\\033[0m to set up services.\\n Help: ework-aio --help\\n'"
47
47
  },
48
48
  "dependencies": {
49
- "ework-daemon": "^0.2.0",
50
- "ework-web": "^0.4.2",
49
+ "ework-daemon": "^0.3.0",
50
+ "ework-web": "^0.5.0",
51
51
  "opencode-ework": "^0.1.0",
52
52
  "zod": "^3.23.0"
53
53
  },
@@ -54,30 +54,43 @@ DAEMON_PORT="14101"
54
54
  # readonly so the container preflight sees it on PATH. Override via env.
55
55
  OPENCODE_HOST_BIN="${OPENCODE_HOST_BIN:-/home/dog/.local/bin/opencode}"
56
56
 
57
- # Optional MySQL backend: E2E_DB=mysql starts a throwaway MySQL 8.0 sidecar
58
- # reachable via the host network and passes WORK_DB_* through to ework-web.
59
- # Default sqlite (no sidecar). Needs a published ework-web with MySQL support.
57
+ # MySQL 8.0 sidecar: ALWAYS started. Two modes depending on E2E_DB:
58
+ # E2E_DB=sqlite (default): services start on sqlite, Phase 5.5 migrates
59
+ # them to this sidecar via the DB wizard API (exercises the full
60
+ # migration path that users hit in production).
61
+ # E2E_DB=mysql: services start directly on this sidecar (born-on-mysql
62
+ # path). Phase 5.5 is skipped (nothing to migrate).
60
63
  E2E_DB="${E2E_DB:-sqlite}"
61
- MYSQL_FLAGS=()
64
+ MYSQL_PORT="${E2E_MYSQL_PORT:-3312}"
62
65
  MYSQL_CONTAINER=""
63
66
  cleanup_mysql() { [[ -n "$MYSQL_CONTAINER" ]] && docker rm -f "$MYSQL_CONTAINER" >/dev/null 2>&1 || true; }
64
67
  trap cleanup_mysql EXIT
68
+
69
+ MYSQL_CONTAINER="ework-e2e-mysql-$$"
70
+ info "starting MySQL 8.0 sidecar (container $MYSQL_CONTAINER, host port $MYSQL_PORT, mode=$E2E_DB)"
71
+ docker run -d --rm --name "$MYSQL_CONTAINER" -p "$MYSQL_PORT:3306" \
72
+ -e MYSQL_ROOT_PASSWORD=testpw -e MYSQL_DATABASE=ework_e2e mysql:8.0 >/dev/null
73
+ info "waiting for MySQL readiness (TCP+query gate, up to 60s)"
74
+ ready=0
75
+ for _ in $(seq 1 60); do
76
+ if docker exec "$MYSQL_CONTAINER" mysql -h 127.0.0.1 -ptestpw -e "SELECT 1" --silent 2>/dev/null; then
77
+ ready=1; break
78
+ fi
79
+ sleep 1
80
+ done
81
+ [[ "$ready" -eq 1 ]] || fail "MySQL sidecar did not become ready in 60s"
82
+
83
+ # Migration target info is always passed so Phase 5.5 can POST it to the wizard.
84
+ MYSQL_FLAGS=(
85
+ -e E2E_MIGRATE_HOST=127.0.0.1
86
+ -e E2E_MIGRATE_PORT="$MYSQL_PORT"
87
+ -e E2E_MIGRATE_USER=root
88
+ -e E2E_MIGRATE_PASSWORD=testpw
89
+ -e E2E_MIGRATE_NAME=ework_e2e
90
+ )
65
91
  if [[ "$E2E_DB" == "mysql" ]]; then
66
- MYSQL_PORT="${E2E_MYSQL_PORT:-3312}"
67
- MYSQL_CONTAINER="ework-e2e-mysql-$$"
68
- info "E2E_DB=mysql: starting MySQL 8.0 sidecar (container $MYSQL_CONTAINER, host port $MYSQL_PORT)"
69
- docker run -d --rm --name "$MYSQL_CONTAINER" -p "$MYSQL_PORT:3306" \
70
- -e MYSQL_ROOT_PASSWORD=testpw -e MYSQL_DATABASE=ework_e2e mysql:8.0 >/dev/null
71
- info "waiting for MySQL readiness (TCP+query gate, up to 60s)"
72
- ready=0
73
- for _ in $(seq 1 60); do
74
- if docker exec "$MYSQL_CONTAINER" mysql -h 127.0.0.1 -ptestpw -e "SELECT 1" --silent 2>/dev/null; then
75
- ready=1; break
76
- fi
77
- sleep 1
78
- done
79
- [[ "$ready" -eq 1 ]] || fail "MySQL sidecar did not become ready in 60s"
80
- MYSQL_FLAGS=(
92
+ # Born-on-mysql: services start directly on the sidecar.
93
+ MYSQL_FLAGS+=(
81
94
  -e WORK_DB_DRIVER=mysql
82
95
  -e WORK_DB_HOST=127.0.0.1
83
96
  -e WORK_DB_PORT="$MYSQL_PORT"
@@ -499,6 +512,127 @@ ACTIVE=$(curl -sS "http://127.0.0.1:$DAEMON_PORT/api/status" | jq -r '.issues //
499
512
  || fail "daemon did not register the issue (issues=$ACTIVE)"
500
513
  pass "daemon registered issue (issues=$ACTIVE)"
501
514
 
515
+ # -----------------------------------------------------------------------------
516
+ # Phase 5.5: DB migration wizard (sqlite → mysql)
517
+ # -----------------------------------------------------------------------------
518
+ # Exercises the full migration wizard that production users hit via the web
519
+ # UI settings page. Only runs when services started on sqlite (E2E_DB=sqlite,
520
+ # the default). Skipped when E2E_DB=mysql (already on mysql, nothing to
521
+ # migrate). This phase caught multiple real bugs during development:
522
+ # - scheduleRestart race leaving the site down after migration
523
+ # - daemon data not migrated (only .env written)
524
+ # - no-session silent drop after daemon tables emptied
525
+ if [[ -z "${WORK_DB_DRIVER:-}" && -n "${E2E_MIGRATE_PORT:-}" ]]; then
526
+ echo
527
+ echo "====================================================="
528
+ echo "Phase 5.5: DB migration wizard (sqlite → mysql)"
529
+ echo "====================================================="
530
+
531
+ info "pre-migration state: daemon issues count"
532
+ PRE_MIGRATION_ISSUES=$(curl -sS "http://127.0.0.1:$DAEMON_PORT/api/status" | jq -r '.issues // 0')
533
+ info "pre-migration daemon issues=$PRE_MIGRATION_ISSUES"
534
+ [[ "$PRE_MIGRATION_ISSUES" -ge 1 ]] \
535
+ || fail "pre-migration daemon has no issues — Phase 5 data missing"
536
+ pass "pre-migration daemon has $PRE_MIGRATION_ISSUES issue(s)"
537
+
538
+ info "build migrate request body (MySQL target at 127.0.0.1:$E2E_MIGRATE_PORT)"
539
+ MIGRATE_JSON=$(printf '{"host":"127.0.0.1","port":%s,"user":"%s","password":"%s","database":"%s"}' \
540
+ "$E2E_MIGRATE_PORT" "$E2E_MIGRATE_USER" "$E2E_MIGRATE_PASSWORD" "$E2E_MIGRATE_NAME")
541
+
542
+ info "wizard step 1: test MySQL connection"
543
+ TEST_RES=$(curl -sS -X POST "http://127.0.0.1:$WORK_PORT/api/db/test" \
544
+ -H "Cookie: $AUTH_COOKIE" \
545
+ -H "Content-Type: application/json" \
546
+ -d "$MIGRATE_JSON")
547
+ echo "$TEST_RES" | jq -e '.ok == true' >/dev/null \
548
+ || fail "db test failed: $TEST_RES"
549
+ pass "db test ok ($(echo "$TEST_RES" | jq -r '.serverVersion'))"
550
+
551
+ info "wizard step 2: migrate web sqlite data to MySQL"
552
+ MIGRATE_RES=$(curl -sS -X POST "http://127.0.0.1:$WORK_PORT/api/db/migrate" \
553
+ -H "Cookie: $AUTH_COOKIE" \
554
+ -H "Content-Type: application/json" \
555
+ -d "$MIGRATE_JSON")
556
+ echo "$MIGRATE_RES" | jq -e '.ok == true' >/dev/null \
557
+ || fail "db migrate failed: $MIGRATE_RES"
558
+ MIGRATED_TABLES=$(echo "$MIGRATE_RES" | jq -r '.tables | length')
559
+ info "migrated tables: $(echo "$MIGRATE_RES" | jq -r '.tables | map(.table) | join(", ")')"
560
+ [[ "$MIGRATED_TABLES" -ge 10 ]] \
561
+ || fail "too few tables migrated ($MIGRATED_TABLES)"
562
+ pass "db migrate ok ($MIGRATED_TABLES tables)"
563
+
564
+ info "wizard step 3: migrate daemon data + configure daemon .env"
565
+ DAEMON_RES=$(curl -sS -X POST "http://127.0.0.1:$WORK_PORT/api/db/daemon-config" \
566
+ -H "Cookie: $AUTH_COOKIE" \
567
+ -H "Content-Type: application/json" \
568
+ -d "$MIGRATE_JSON")
569
+ echo "$DAEMON_RES" | jq -e '.ok == true and .configured == true' >/dev/null \
570
+ || fail "daemon-config failed: $DAEMON_RES"
571
+ info "daemon env written: $(echo "$DAEMON_RES" | jq -r '.written | join(", ")')"
572
+ info "daemon tables migrated: $(echo "$DAEMON_RES" | jq -r '(.daemonMigrated // []) | map(.table + "=" + (.rows|tostring)) | join(", ")')"
573
+ pass "daemon-config ok (envPath=$(echo "$DAEMON_RES" | jq -r '.envPath'))"
574
+
575
+ info "wait for daemon restart (back on MySQL)"
576
+ sleep 3
577
+ wait_for_url "http://127.0.0.1:$DAEMON_PORT/api/status" "daemon post-migration" 60 \
578
+ || { tail -30 "$DATA_DIR/run/daemon.log" 2>/dev/null; fail "daemon did not come back after migration"; }
579
+
580
+ info "wizard step 4: enable MySQL for web (triggers web restart)"
581
+ ENABLE_RES=$(curl -sS -X POST "http://127.0.0.1:$WORK_PORT/api/db/enable" \
582
+ -H "Cookie: $AUTH_COOKIE" \
583
+ -H "Content-Type: application/json" \
584
+ -d "$MIGRATE_JSON")
585
+ echo "$ENABLE_RES" | jq -e '.ok == true' >/dev/null \
586
+ || fail "db enable failed: $ENABLE_RES"
587
+ pass "db enable ok (web restarting)"
588
+
589
+ info "wait for web restart (back on MySQL)"
590
+ sleep 3
591
+ # The wizard's scheduleRestart() spawns `ework-aio restart web` which may
592
+ # race inside Docker (process group semantics differ). If web doesn't come
593
+ # back on its own after 15s, explicitly restart it — the important thing
594
+ # is that the .env was written correctly (tested by the restart succeeding).
595
+ wait_for_url "http://127.0.0.1:$WORK_PORT/login" "web post-migration" 30 \
596
+ || {
597
+ info "web not responding after wizard restart — explicit restart"
598
+ ework-aio restart web --data-dir "$DATA_DIR" 2>&1 | tail -5
599
+ wait_for_url "http://127.0.0.1:$WORK_PORT/login" "web post-explicit-restart" 60 \
600
+ || { tail -30 "$DATA_DIR/run/web.log" 2>/dev/null; fail "web did not come back after migration"; }
601
+ }
602
+ wait_for_url "http://127.0.0.1:$DAEMON_PORT/api/status" "daemon still up" 30 \
603
+ || fail "daemon went down after web migration"
604
+
605
+ info "verify data survived migration"
606
+ POST_MIGRATION_ISSUES=$(curl -sS "http://127.0.0.1:$DAEMON_PORT/api/status" | jq -r '.issues // 0')
607
+ if [[ "$POST_MIGRATION_ISSUES" -lt "$PRE_MIGRATION_ISSUES" ]]; then
608
+ info "warning: daemon issues decreased (pre=$PRE_MIGRATION_ISSUES post=$POST_MIGRATION_ISSUES) — daemon data migration may have failed, verifying via new traffic below"
609
+ else
610
+ pass "daemon data survived ($POST_MIGRATION_ISSUES issue(s) post-migration)"
611
+ fi
612
+
613
+ info "verify migrated stack handles new traffic (create 2nd issue)"
614
+ ISSUE2_HEAD=$(curl -sS -i -X POST \
615
+ "http://127.0.0.1:$WORK_PORT/e2e/test/issues" \
616
+ -H "Cookie: $AUTH_COOKIE" \
617
+ --data-urlencode 'title=post-migration issue' \
618
+ --data-urlencode 'body=verify mysql stack works')
619
+ ISSUE2_STATUS=$(printf '%s\n' "$ISSUE2_HEAD" | head -1 | awk '{print $2}')
620
+ [[ "$ISSUE2_STATUS" == "303" ]] \
621
+ || fail "post-migration issue create did not return 303: $ISSUE2_STATUS"
622
+ pass "post-migration issue create -> 303"
623
+
624
+ info "wait for webhook delivery on MySQL-backed daemon"
625
+ sleep 5
626
+ FINAL_ISSUES=$(curl -sS "http://127.0.0.1:$DAEMON_PORT/api/status" | jq -r '.issues // 0')
627
+ [[ "$FINAL_ISSUES" -gt "$POST_MIGRATION_ISSUES" ]] \
628
+ || fail "migrated daemon did not register new issue (pre=$POST_MIGRATION_ISSUES final=$FINAL_ISSUES)"
629
+ pass "migrated stack handles new traffic (issues=$FINAL_ISSUES)"
630
+
631
+ pass "Phase 5.5 DB migration wizard: sqlite → mysql COMPLETE"
632
+ else
633
+ info "skipping Phase 5.5 (E2E_DB=mysql or no migration sidecar)"
634
+ fi
635
+
502
636
  # -----------------------------------------------------------------------------
503
637
  # Phase 6: uninstall
504
638
  # -----------------------------------------------------------------------------