ework-aio 0.1.7 → 0.1.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/README.md +46 -1
- package/bin/ework-aio +377 -3
- package/package.json +2 -1
- package/scripts/backfill-timestamps.ts +225 -0
- package/scripts/migrate-from-gitea.ts +640 -0
- package/scripts/regression.sh +249 -0
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ Keeping these in a separate, explicitly-invoked step is intentional:
|
|
|
100
100
|
```
|
|
101
101
|
ework-aio install [options] Install or upgrade the stack
|
|
102
102
|
ework-aio uninstall Stop services and remove units (data preserved)
|
|
103
|
-
ework-aio status Show service status
|
|
103
|
+
ework-aio status Show service status (systemd mode)
|
|
104
104
|
ework-aio logs [web|daemon] Tail logs
|
|
105
105
|
ework-aio env Print key paths (no secrets)
|
|
106
106
|
ework-aio config <subcommand> Read / change runtime .env keys
|
|
@@ -109,6 +109,12 @@ ework-aio config <subcommand> Read / change runtime .env keys
|
|
|
109
109
|
config set <KEY> <VALUE> Set a key, then restart affected service
|
|
110
110
|
(use --no-restart to defer)
|
|
111
111
|
config restart <web|daemon|both> Restart one or both services
|
|
112
|
+
|
|
113
|
+
# PID-file mode (no systemd required — see below)
|
|
114
|
+
ework-aio start [web|daemon|both] [--foreground] Start services in background
|
|
115
|
+
ework-aio stop [web|daemon|both] Stop services (SIGTERM, 5s, SIGKILL)
|
|
116
|
+
ework-aio restart [web|daemon|both] Stop + start
|
|
117
|
+
ework-aio ps Show PID-file mode status
|
|
112
118
|
```
|
|
113
119
|
|
|
114
120
|
### Install options
|
|
@@ -212,6 +218,45 @@ npm uninstall -g ework-aio ework-web ework-daemon opencode-ework
|
|
|
212
218
|
# Remove the plugin entry from ~/.config/opencode/opencode.json manually
|
|
213
219
|
```
|
|
214
220
|
|
|
221
|
+
## PID-file mode (no systemd)
|
|
222
|
+
|
|
223
|
+
`install` / `status` / `config` / `uninstall` go through `systemctl`, which assumes systemd. If you're on a system without systemd — **macOS, WSL1, Alpine without systemd, Docker containers, dev laptops** — use the parallel PID-file mode instead:
|
|
224
|
+
|
|
225
|
+
| Mode | Start | Stop | Status | Restart |
|
|
226
|
+
| ----------- | ----------------------------- | ----------------------------- | ------------------- | -------------------------------- |
|
|
227
|
+
| systemd | `ework-aio install` | `ework-aio uninstall` | `ework-aio status` | `ework-aio config restart both` |
|
|
228
|
+
| **PID-file**| `ework-aio start` | `ework-aio stop` | `ework-aio ps` | `ework-aio restart` |
|
|
229
|
+
|
|
230
|
+
PID-file mode writes per-service PID + log under `~/.local/share/ework-aio/run/`:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
~/.local/share/ework-aio/run/
|
|
234
|
+
├── web.pid # ework-web PID
|
|
235
|
+
├── web.log # nohup stdout+stderr
|
|
236
|
+
├── daemon.pid
|
|
237
|
+
└── daemon.log
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# One-time setup: scaffold .env + data dir (no systemd required if you skip the unit install)
|
|
242
|
+
ework-aio install --no-start
|
|
243
|
+
|
|
244
|
+
# Then control with PID-file mode:
|
|
245
|
+
ework-aio start # start web + daemon in background
|
|
246
|
+
ework-aio start web # just web
|
|
247
|
+
ework-aio start daemon --foreground # run in current terminal
|
|
248
|
+
ework-aio stop # SIGTERM, 5s grace, SIGKILL if needed
|
|
249
|
+
ework-aio restart web # stop + start
|
|
250
|
+
ework-aio ps # show PID + log path for each service
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Override the data dir with `EWORK_AIO_DATA_DIR=/path ework-aio start`.
|
|
254
|
+
|
|
255
|
+
**Notes**:
|
|
256
|
+
- `install` (with `--no-start`) is still required once to scaffold `.env` and the data directory. The systemd units it writes are inert when nothing manages them.
|
|
257
|
+
- PID-file mode does NOT auto-restart on crash. Consider a process supervisor (`pm2`, `supervisord`, `runit`, `launchd`) if you need that.
|
|
258
|
+
- Both modes can coexist (systemd-managed services ignore PID files, and vice versa) but you should pick one to avoid port conflicts.
|
|
259
|
+
|
|
215
260
|
## Alternatives
|
|
216
261
|
|
|
217
262
|
- **Docker AIO**: the `ework-web` repo ships `docker/build.sh` + `docker/run.sh` for a single-container deployment. Useful when you don't want to manage host systemd.
|
package/bin/ework-aio
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { spawn, spawnSync } from "bun";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "fs";
|
|
11
|
+
import { dirname, join, resolve } from "path";
|
|
5
12
|
import { fileURLToPath } from "url";
|
|
6
13
|
|
|
7
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -14,8 +21,9 @@ function usage() {
|
|
|
14
21
|
|
|
15
22
|
Usage:
|
|
16
23
|
ework-aio install [options] Install and start ework-web + ework-daemon
|
|
24
|
+
(uses systemd; see start/stop for non-systemd)
|
|
17
25
|
ework-aio uninstall Stop services and remove units (keeps data)
|
|
18
|
-
ework-aio status Show service status
|
|
26
|
+
ework-aio status Show service status (systemd mode)
|
|
19
27
|
ework-aio logs [svc] Tail logs (svc: web | daemon)
|
|
20
28
|
ework-aio env Print the .env path and key names (no values)
|
|
21
29
|
ework-aio config <subcommand> Read / change runtime .env keys
|
|
@@ -24,6 +32,19 @@ Usage:
|
|
|
24
32
|
config set <KEY> <VALUE> Set a key, then restart affected service
|
|
25
33
|
(use --no-restart to defer)
|
|
26
34
|
config restart <web|daemon|both>
|
|
35
|
+
ework-aio start [svc] Start services in background WITHOUT systemd
|
|
36
|
+
(PID-file mode, works on WSL/macOS/Alpine).
|
|
37
|
+
svc: web | daemon | both (default both).
|
|
38
|
+
--foreground runs in current terminal.
|
|
39
|
+
ework-aio stop [svc] Stop services started via 'start'
|
|
40
|
+
(SIGTERM, 5s grace, then SIGKILL)
|
|
41
|
+
ework-aio restart [svc] Stop + start
|
|
42
|
+
ework-aio ps Show PID-file mode status (web+daemon)
|
|
43
|
+
ework-aio migrate [options] Migrate issues from a Gitea instance into
|
|
44
|
+
ework-web via the Gitea REST API. Idempotent.
|
|
45
|
+
Run \`ework-aio migrate --help\` for details.
|
|
46
|
+
ework-aio backfill-timestamps Fix created_at/updated_at on already-migrated
|
|
47
|
+
data. Use after migrate when ework.db is local.
|
|
27
48
|
ework-aio --version Print version
|
|
28
49
|
|
|
29
50
|
Install options:
|
|
@@ -35,6 +56,9 @@ Install options:
|
|
|
35
56
|
--bot-name <login> Bot username (default: ework-daemon)
|
|
36
57
|
--no-start Install units but don't start services
|
|
37
58
|
--yes Skip all prompts (use generated defaults)
|
|
59
|
+
|
|
60
|
+
PID-file mode (--foreground works with start):
|
|
61
|
+
--data-dir <path> Same as install option
|
|
38
62
|
`);
|
|
39
63
|
}
|
|
40
64
|
|
|
@@ -56,6 +80,356 @@ if (!existsSync(installSh)) {
|
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
const knownSubs = ["install", "uninstall", "status", "logs", "env", "config"];
|
|
83
|
+
|
|
84
|
+
// ─── PID-file mode (no systemd required) ───────────────────────────────────
|
|
85
|
+
//
|
|
86
|
+
// start/stop/restart/ps bypass install.sh and the systemd dependency entirely.
|
|
87
|
+
// Each service writes its PID + log under $DATA_DIR/run/, managed via the
|
|
88
|
+
// standard node:child_process detached-spawn pattern (sets the child free of
|
|
89
|
+
// the parent's process group so the parent can exit while the service keeps
|
|
90
|
+
// running). This is the path for systems without systemd: macOS, WSL1, Alpine,
|
|
91
|
+
// containers, dev laptops.
|
|
92
|
+
|
|
93
|
+
const SVC_CONFIG = {
|
|
94
|
+
web: {
|
|
95
|
+
binName: "ework-web",
|
|
96
|
+
binFallback: "ework-web/bin/ework-web.js",
|
|
97
|
+
dataSubdir: "ework-web",
|
|
98
|
+
label: "ework-web",
|
|
99
|
+
},
|
|
100
|
+
daemon: {
|
|
101
|
+
binName: "ework-daemon-server",
|
|
102
|
+
binFallback: "ework-daemon/bin/ework-daemon-server.js",
|
|
103
|
+
dataSubdir: "ework-daemon",
|
|
104
|
+
label: "ework-daemon",
|
|
105
|
+
},
|
|
106
|
+
} as const;
|
|
107
|
+
|
|
108
|
+
type SvcName = keyof typeof SVC_CONFIG;
|
|
109
|
+
|
|
110
|
+
function resolveDataDir(): string {
|
|
111
|
+
const xdg = process.env.XDG_DATA_HOME || `${process.env.HOME}/.local/share`;
|
|
112
|
+
return process.env.EWORK_AIO_DATA_DIR || `${xdg}/ework-aio`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function runDir(): string {
|
|
116
|
+
const d = join(resolveDataDir(), "run");
|
|
117
|
+
if (!existsSync(d)) mkdirSync(d, { recursive: true });
|
|
118
|
+
return d;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function svcEnvPath(svc: SvcName): string {
|
|
122
|
+
return join(resolveDataDir(), SVC_CONFIG[svc].dataSubdir, ".env");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function svcPidPath(svc: SvcName): string {
|
|
126
|
+
return join(runDir(), `${svc}.pid`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function svcLogPath(svc: SvcName): string {
|
|
130
|
+
return join(runDir(), `${svc}.log`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// KEY=VALUE parser, ignores comments + blanks, never throws.
|
|
134
|
+
function parseEnvFile(path: string): Record<string, string> {
|
|
135
|
+
const out: Record<string, string> = {};
|
|
136
|
+
if (!existsSync(path)) return out;
|
|
137
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
138
|
+
const trimmed = line.trim();
|
|
139
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
140
|
+
const eq = trimmed.indexOf("=");
|
|
141
|
+
if (eq < 0) continue;
|
|
142
|
+
const k = trimmed.slice(0, eq).trim();
|
|
143
|
+
let v = trimmed.slice(eq + 1).trim();
|
|
144
|
+
if (
|
|
145
|
+
(v.startsWith('"') && v.endsWith('"')) ||
|
|
146
|
+
(v.startsWith("'") && v.endsWith("'"))
|
|
147
|
+
) {
|
|
148
|
+
v = v.slice(1, -1);
|
|
149
|
+
}
|
|
150
|
+
if (k) out[k] = v;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveSvcBin(svc: SvcName): string | null {
|
|
156
|
+
const cfg = SVC_CONFIG[svc];
|
|
157
|
+
// PATH lookup first (covers PATH-installed global bins). `command -v` is a
|
|
158
|
+
// shell builtin so we have to invoke via sh -c.
|
|
159
|
+
const lookup = spawnSync(["sh", "-c", `command -v ${cfg.binName}`], {
|
|
160
|
+
env: process.env,
|
|
161
|
+
stdout: "pipe",
|
|
162
|
+
stderr: "ignore",
|
|
163
|
+
});
|
|
164
|
+
if (lookup.success) {
|
|
165
|
+
const p = lookup.stdout.toString().trim();
|
|
166
|
+
if (p && existsSync(p)) return p;
|
|
167
|
+
}
|
|
168
|
+
// npm global root fallback — covers npm-installed packages whose bin dir
|
|
169
|
+
// isn't on PATH (common with nvm-style installs).
|
|
170
|
+
const npmRoot = spawnSync(["npm", "root", "-g"], {
|
|
171
|
+
env: process.env,
|
|
172
|
+
stdout: "pipe",
|
|
173
|
+
stderr: "ignore",
|
|
174
|
+
});
|
|
175
|
+
if (npmRoot.success) {
|
|
176
|
+
const fallback = join(npmRoot.stdout.toString().trim(), cfg.binFallback);
|
|
177
|
+
if (existsSync(fallback)) return fallback;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function pidAlive(pid: number): boolean {
|
|
183
|
+
if (!pid || pid <= 0) return false;
|
|
184
|
+
try {
|
|
185
|
+
process.kill(pid, 0);
|
|
186
|
+
return true;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readPid(svc: SvcName): number | null {
|
|
193
|
+
const p = svcPidPath(svc);
|
|
194
|
+
if (!existsSync(p)) return null;
|
|
195
|
+
const raw = readFileSync(p, "utf8").trim();
|
|
196
|
+
const n = Number(raw);
|
|
197
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function writePid(svc: SvcName, pid: number): void {
|
|
201
|
+
writeFileSync(svcPidPath(svc), `${pid}\n`, { encoding: "utf8" });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function clearPid(svc: SvcName): void {
|
|
205
|
+
const p = svcPidPath(svc);
|
|
206
|
+
if (existsSync(p)) rmSync(p);
|
|
207
|
+
|
|
208
|
+
const staleSocket = join(runDir(), `${svc}.port`);
|
|
209
|
+
if (existsSync(staleSocket)) rmSync(staleSocket);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function waitForExit(pid: number, timeoutMs: number): boolean {
|
|
213
|
+
const deadline = Date.now() + timeoutMs;
|
|
214
|
+
while (Date.now() < deadline) {
|
|
215
|
+
if (!pidAlive(pid)) return true;
|
|
216
|
+
const slept = Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
217
|
+
void slept;
|
|
218
|
+
}
|
|
219
|
+
return !pidAlive(pid);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseSvcArg(argv: string[]): { svc: "both" | SvcName; foreground: boolean } {
|
|
223
|
+
let svc: "both" | SvcName = "both";
|
|
224
|
+
let foreground = false;
|
|
225
|
+
for (const a of argv.slice(1)) {
|
|
226
|
+
if (a === "--foreground" || a === "-f") foreground = true;
|
|
227
|
+
else if (a === "web" || a === "daemon") svc = a;
|
|
228
|
+
else if (a === "both") svc = "both";
|
|
229
|
+
else {
|
|
230
|
+
console.error(`Unknown argument: ${a}`);
|
|
231
|
+
console.error("Usage: ework-aio <start|stop|restart> [web|daemon|both] [--foreground]");
|
|
232
|
+
process.exit(2);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { svc, foreground };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function startOne(svc: SvcName, foreground: boolean): boolean {
|
|
239
|
+
const existing = readPid(svc);
|
|
240
|
+
if (existing && pidAlive(existing)) {
|
|
241
|
+
console.log(`${SVC_CONFIG[svc].label} already running (pid ${existing})`);
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (existing) clearPid(svc);
|
|
245
|
+
|
|
246
|
+
const bin = resolveSvcBin(svc);
|
|
247
|
+
if (!bin) {
|
|
248
|
+
console.error(
|
|
249
|
+
`${SVC_CONFIG[svc].label}: cannot find bin '${SVC_CONFIG[svc].binName}' in PATH or npm global root.`
|
|
250
|
+
);
|
|
251
|
+
console.error(` Install with: npm i -g ${SVC_CONFIG[svc].binName.split("-")[0]}`);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const envPath = svcEnvPath(svc);
|
|
255
|
+
if (!existsSync(envPath)) {
|
|
256
|
+
console.error(
|
|
257
|
+
`${SVC_CONFIG[svc].label}: missing .env at ${envPath}.`
|
|
258
|
+
);
|
|
259
|
+
console.error(" Run `ework-aio install --no-start` first to scaffold config + data dir.");
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Sane perms on the bin (npm sometimes ships without +x).
|
|
264
|
+
try {
|
|
265
|
+
chmodSync(bin, 0o755);
|
|
266
|
+
} catch {
|
|
267
|
+
// best-effort
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const mergedEnv = { ...process.env, ...parseEnvFile(envPath) };
|
|
271
|
+
const logPath = svcLogPath(svc);
|
|
272
|
+
|
|
273
|
+
if (foreground) {
|
|
274
|
+
console.log(`Starting ${SVC_CONFIG[svc].label} in foreground (Ctrl+C to stop)`);
|
|
275
|
+
const r = spawnSync([bin], {
|
|
276
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
277
|
+
env: mergedEnv,
|
|
278
|
+
});
|
|
279
|
+
return r.status === 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Background: detached spawn via node:child_process so the parent can exit
|
|
283
|
+
// cleanly while the service keeps running. stdout+stderr append to logPath.
|
|
284
|
+
const { spawn: nodeSpawn } = require("node:child_process");
|
|
285
|
+
const { openSync } = require("node:fs");
|
|
286
|
+
const logFd = openSync(logPath, "a");
|
|
287
|
+
const child = nodeSpawn(bin, [], {
|
|
288
|
+
env: mergedEnv,
|
|
289
|
+
detached: true,
|
|
290
|
+
stdio: ["ignore", logFd, logFd],
|
|
291
|
+
});
|
|
292
|
+
child.unref();
|
|
293
|
+
if (!child.pid) {
|
|
294
|
+
console.error(`${SVC_CONFIG[svc].label}: spawn failed`);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
writePid(svc, child.pid);
|
|
298
|
+
console.log(`${SVC_CONFIG[svc].label} started (pid ${child.pid}, log ${logPath})`);
|
|
299
|
+
|
|
300
|
+
// Grace window: if the process dies within 800ms it almost certainly failed
|
|
301
|
+
// to start (bad env, port in use, missing dep). Surface the last log lines.
|
|
302
|
+
const start = Date.now();
|
|
303
|
+
while (Date.now() - start < 800) {
|
|
304
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 80);
|
|
305
|
+
}
|
|
306
|
+
if (!pidAlive(child.pid)) {
|
|
307
|
+
console.error(`${SVC_CONFIG[svc].label}: exited within 800ms — likely a startup error.`);
|
|
308
|
+
console.error(` Tail of ${logPath}:`);
|
|
309
|
+
const tail = spawnSync(["tail", "-n", "20", logPath], { stdout: "pipe" });
|
|
310
|
+
if (tail.status === 0) console.error(tail.stdout.toString());
|
|
311
|
+
clearPid(svc);
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function stopOne(svc: SvcName): boolean {
|
|
318
|
+
const pid = readPid(svc);
|
|
319
|
+
if (!pid) {
|
|
320
|
+
console.log(`${SVC_CONFIG[svc].label} not running (no PID file)`);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
if (!pidAlive(pid)) {
|
|
324
|
+
console.log(`${SVC_CONFIG[svc].label} not running (stale PID file, removing)`);
|
|
325
|
+
clearPid(svc);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
process.kill(pid, "SIGTERM");
|
|
330
|
+
} catch (e) {
|
|
331
|
+
console.error(`${SVC_CONFIG[svc].label}: SIGTERM failed: ${(e as Error).message}`);
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
console.log(`${SVC_CONFIG[svc].label}: SIGTERM sent (pid ${pid})`);
|
|
335
|
+
if (waitForExit(pid, 5000)) {
|
|
336
|
+
clearPid(svc);
|
|
337
|
+
console.log(`${SVC_CONFIG[svc].label} stopped`);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
console.warn(`${SVC_CONFIG[svc].label}: did not exit in 5s, sending SIGKILL`);
|
|
341
|
+
try {
|
|
342
|
+
process.kill(pid, "SIGKILL");
|
|
343
|
+
} catch {
|
|
344
|
+
// best-effort
|
|
345
|
+
}
|
|
346
|
+
if (waitForExit(pid, 2000)) {
|
|
347
|
+
clearPid(svc);
|
|
348
|
+
console.log(`${SVC_CONFIG[svc].label} killed`);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
console.error(`${SVC_CONFIG[svc].label}: failed to kill pid ${pid}`);
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function psAll(): void {
|
|
356
|
+
console.log("ework-aio PID-file mode status:");
|
|
357
|
+
for (const svc of Object.keys(SVC_CONFIG) as SvcName[]) {
|
|
358
|
+
const pid = readPid(svc);
|
|
359
|
+
const alive = pid !== null && pidAlive(pid);
|
|
360
|
+
const cfg = SVC_CONFIG[svc];
|
|
361
|
+
if (alive) {
|
|
362
|
+
console.log(` ${cfg.label.padEnd(18)} running pid ${pid} log ${svcLogPath(svc)}`);
|
|
363
|
+
} else if (pid !== null) {
|
|
364
|
+
console.log(` ${cfg.label.padEnd(18)} dead stale pid ${pid} (run: ework-aio stop ${svc})`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(` ${cfg.label.padEnd(18)} stopped`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function runPidMode(command: "start" | "stop" | "restart", argv: string[]): number {
|
|
372
|
+
const { svc, foreground } = parseSvcArg(argv);
|
|
373
|
+
if (command === "ps") {
|
|
374
|
+
psAll();
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
377
|
+
const targets: SvcName[] = svc === "both" ? ["web", "daemon"] : [svc];
|
|
378
|
+
let rc = 0;
|
|
379
|
+
if (command === "stop") {
|
|
380
|
+
for (const s of targets) if (!stopOne(s)) rc = 1;
|
|
381
|
+
return rc;
|
|
382
|
+
}
|
|
383
|
+
if (command === "start") {
|
|
384
|
+
for (const s of targets) if (!startOne(s, foreground)) rc = 1;
|
|
385
|
+
return rc;
|
|
386
|
+
}
|
|
387
|
+
// restart
|
|
388
|
+
for (const s of targets) stopOne(s);
|
|
389
|
+
for (const s of targets) if (!startOne(s, foreground)) rc = 1;
|
|
390
|
+
return rc;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
subcommand === "start" ||
|
|
395
|
+
subcommand === "stop" ||
|
|
396
|
+
subcommand === "restart" ||
|
|
397
|
+
subcommand === "ps"
|
|
398
|
+
) {
|
|
399
|
+
process.exit(runPidMode(subcommand as "start" | "stop" | "restart", args));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (subcommand === "migrate") {
|
|
403
|
+
const migrateScript = resolve(__dirname, "..", "scripts", "migrate-from-gitea.ts");
|
|
404
|
+
if (!existsSync(migrateScript)) {
|
|
405
|
+
console.error(`Cannot find migrate script (looked at ${migrateScript}).`);
|
|
406
|
+
console.error("This usually means the package was installed incorrectly.");
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
const rest = args.slice(1);
|
|
410
|
+
const result = spawnSync(["bun", migrateScript, ...rest], {
|
|
411
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
412
|
+
env: process.env,
|
|
413
|
+
});
|
|
414
|
+
if (result.status !== null) process.exit(result.status);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (subcommand === "backfill-timestamps") {
|
|
419
|
+
const script = resolve(__dirname, "..", "scripts", "backfill-timestamps.ts");
|
|
420
|
+
if (!existsSync(script)) {
|
|
421
|
+
console.error(`Cannot find backfill-timestamps script (looked at ${script}).`);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
const rest = args.slice(1);
|
|
425
|
+
const result = spawnSync(["bun", script, ...rest], {
|
|
426
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
427
|
+
env: process.env,
|
|
428
|
+
});
|
|
429
|
+
if (result.status !== null) process.exit(result.status);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
|
|
59
433
|
const finalArgs = knownSubs.includes(subcommand) ? args : ["install", ...args];
|
|
60
434
|
|
|
61
435
|
const result = spawnSync(["bash", installSh, ...finalArgs], {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ework-aio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.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",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"bin",
|
|
29
|
+
"scripts",
|
|
29
30
|
"src",
|
|
30
31
|
"README.md",
|
|
31
32
|
"LICENSE"
|