ework-aio 0.5.19 → 0.5.21
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 +1 -1
- package/src/commands/install.ts +41 -2
- package/src/commands/lifecycle.ts +3 -18
- package/src/config.ts +21 -6
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ework-aio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.21",
|
|
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",
|
package/src/commands/install.ts
CHANGED
|
@@ -178,8 +178,9 @@ export async function runInstall(
|
|
|
178
178
|
// 5. Write (or forward-fill) web .env.
|
|
179
179
|
const ctx: InstallContext = {
|
|
180
180
|
paths,
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
workPort: opts.workPort,
|
|
182
|
+
daemonPort: opts.daemonPort,
|
|
183
|
+
routerPort: DEFAULTS.routerPort,
|
|
183
184
|
botName: opts.botName,
|
|
184
185
|
operatorLogin,
|
|
185
186
|
opencodeBin,
|
|
@@ -463,6 +464,44 @@ export async function runInstall(
|
|
|
463
464
|
}
|
|
464
465
|
}
|
|
465
466
|
|
|
467
|
+
// 12.5 Write router .env + start ework-router (webhook routing layer).
|
|
468
|
+
const routerEnvResult = await ensureEnvFile({ file: "router", filePath: paths.routerEnvFile, ctx });
|
|
469
|
+
if (routerEnvResult.created) {
|
|
470
|
+
logger.ok(`wrote ${paths.routerEnvFile}`);
|
|
471
|
+
} else if (routerEnvResult.added.length > 0) {
|
|
472
|
+
logger.ok(`forward-filled ${paths.routerEnvFile}: +${routerEnvResult.added.length} keys`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
let routerStarted = false;
|
|
476
|
+
if (opts.noStart) {
|
|
477
|
+
logger.warn(`--no-start: skipping ework-router startup`);
|
|
478
|
+
} else {
|
|
479
|
+
const routerPid = await readPidFile(paths.routerPidFile);
|
|
480
|
+
if (routerPid !== null && isProcessRunning(routerPid)) {
|
|
481
|
+
routerStarted = true;
|
|
482
|
+
logger.log(`ework-router already running (pid ${routerPid})`);
|
|
483
|
+
}
|
|
484
|
+
if (!routerStarted) {
|
|
485
|
+
const routerBin = resolveBundledBin("ework-router", "bin/ework-router.js");
|
|
486
|
+
if (routerBin) {
|
|
487
|
+
logger.log(`starting ework-router (PID-file mode)...`);
|
|
488
|
+
const env = await loadEnvIntoProcess(paths.routerEnvFile);
|
|
489
|
+
const { pid } = await startProcess({
|
|
490
|
+
cmd: "bun",
|
|
491
|
+
args: [routerBin],
|
|
492
|
+
cwd: paths.routerDataDir,
|
|
493
|
+
env,
|
|
494
|
+
logFile: paths.routerLogFile,
|
|
495
|
+
pidFile: paths.routerPidFile,
|
|
496
|
+
});
|
|
497
|
+
logger.ok(`ework-router started (pid ${pid}, log ${paths.routerLogFile})`);
|
|
498
|
+
routerStarted = true;
|
|
499
|
+
} else {
|
|
500
|
+
logger.warn(`ework-router binary not found — router skipped (install with: npm install -g ework-router)`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
466
505
|
// 13. Register opencode-ework plugin in opencode.json (idempotent).
|
|
467
506
|
const { added: pluginAdded } = await ensurePluginInFile(paths.opencodeConfigFile, PLUGIN_NAME);
|
|
468
507
|
if (pluginAdded) {
|
|
@@ -130,9 +130,10 @@ export async function startFromSp(
|
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
const env = await loadEnv(sp.envFile);
|
|
133
|
+
const useBun = binPath.endsWith(".js");
|
|
133
134
|
const { pid } = await startProcess({
|
|
134
|
-
cmd: binPath,
|
|
135
|
-
args: [],
|
|
135
|
+
cmd: useBun ? "bun" : binPath,
|
|
136
|
+
args: useBun ? [binPath] : [],
|
|
136
137
|
cwd: sp.dataDir,
|
|
137
138
|
env,
|
|
138
139
|
logFile: sp.logFile,
|
|
@@ -240,14 +241,6 @@ export async function runStart(
|
|
|
240
241
|
): Promise<void> {
|
|
241
242
|
const paths = resolvePaths({ dataDir: opts.dataDir, scope: opts.scope, useSystemd: false });
|
|
242
243
|
for (const { sp, label } of iterTargets(paths, target)) {
|
|
243
|
-
if (label === "router") {
|
|
244
|
-
try {
|
|
245
|
-
await startFromSp(sp, label, logger);
|
|
246
|
-
} catch (e) {
|
|
247
|
-
logger.log(`ework-router skipped (${(e as Error).message})`);
|
|
248
|
-
}
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
244
|
await startFromSp(sp, label, logger);
|
|
252
245
|
}
|
|
253
246
|
}
|
|
@@ -271,14 +264,6 @@ export async function runRestart(
|
|
|
271
264
|
const paths = resolvePaths({ dataDir: opts.dataDir, scope: opts.scope, useSystemd: false });
|
|
272
265
|
for (const { sp, label } of iterTargets(paths, target)) {
|
|
273
266
|
await stopFromSp(sp, label, logger);
|
|
274
|
-
if (label === "router") {
|
|
275
|
-
try {
|
|
276
|
-
await startFromSp(sp, label, logger);
|
|
277
|
-
} catch (e) {
|
|
278
|
-
logger.log(`ework-router skipped (${(e as Error).message})`);
|
|
279
|
-
}
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
267
|
await startFromSp(sp, label, logger);
|
|
283
268
|
}
|
|
284
269
|
}
|
package/src/config.ts
CHANGED
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
import { randomBytes } from "node:crypto";
|
|
18
18
|
import type { PathConfig } from "./paths.ts";
|
|
19
19
|
|
|
20
|
-
export type EnvFile = "web" | "daemon";
|
|
20
|
+
export type EnvFile = "web" | "daemon" | "router";
|
|
21
21
|
|
|
22
22
|
export interface InstallContext {
|
|
23
23
|
paths: PathConfig;
|
|
24
24
|
workPort: number;
|
|
25
25
|
daemonPort: number;
|
|
26
|
+
routerPort: number;
|
|
26
27
|
botName: string;
|
|
27
28
|
operatorLogin: string;
|
|
28
29
|
// opencode binary path (looked up via preflight)
|
|
@@ -73,7 +74,7 @@ export const WEB_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
|
73
74
|
// user can't append → "EACCES: permission denied" on every request.
|
|
74
75
|
{ envVar: "WORK_ACCESS_LOG", file: "web", generate: (c) => `${c.paths.runDir}/web-access.log` },
|
|
75
76
|
{ envVar: "WORK_DAEMON_BOT_LOGIN", file: "web", generate: (c) => c.botName },
|
|
76
|
-
{ envVar: "WORK_DAEMON_WEBHOOK_URL", file: "web", generate: (c) => `http://127.0.0.1:${c.
|
|
77
|
+
{ envVar: "WORK_DAEMON_WEBHOOK_URL", file: "web", generate: (c) => `http://127.0.0.1:${c.routerPort}` },
|
|
77
78
|
{ envVar: "WORK_DAEMON_WEBHOOK_SECRET", file: "web", secret: true, generate: sharedWebhookSecret },
|
|
78
79
|
] as const;
|
|
79
80
|
|
|
@@ -94,20 +95,34 @@ export const DAEMON_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
|
94
95
|
{ envVar: "OPENCODE_BASE_WORKDIR", file: "daemon", generate: (c) => c.paths.opencodeWorkdir },
|
|
95
96
|
] as const;
|
|
96
97
|
|
|
98
|
+
export const ROUTER_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
99
|
+
{ envVar: "ROUTER_ENV", file: "router", generate: () => "production" },
|
|
100
|
+
{ envVar: "ROUTER_PORT", file: "router", generate: (c) => String(c.routerPort) },
|
|
101
|
+
{ envVar: "ROUTER_HOST", file: "router", generate: () => "127.0.0.1" },
|
|
102
|
+
{ envVar: "ROUTER_STRATEGY", file: "router", generate: () => "least-loaded" },
|
|
103
|
+
{ envVar: "WORK_DB_DRIVER", file: "router", generate: () => "sqlite" },
|
|
104
|
+
{ envVar: "WORK_DB_PATH", file: "router", generate: (c) => c.paths.webDbPath },
|
|
105
|
+
{ envVar: "WORK_DB_PREFIX", file: "router", generate: () => "" },
|
|
106
|
+
{ envVar: "DAEMON_TABLE_PREFIX", file: "router", generate: () => "d_" },
|
|
107
|
+
] as const;
|
|
108
|
+
|
|
97
109
|
export function keysForFile(file: EnvFile): readonly EnvKeySpec[] {
|
|
98
|
-
|
|
110
|
+
if (file === "web") return WEB_ENV_KEYS;
|
|
111
|
+
if (file === "router") return ROUTER_ENV_KEYS;
|
|
112
|
+
return DAEMON_ENV_KEYS;
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
// Set of env vars that, if missing, would crash ework-web
|
|
102
|
-
// at startup (Zod schema rejects). Used by env.ts forward-fill
|
|
103
|
-
// which keys were injected.
|
|
115
|
+
// Set of env vars that, if missing, would crash ework-web, ework-daemon, or
|
|
116
|
+
// ework-router at startup (Zod schema rejects). Used by env.ts forward-fill.
|
|
104
117
|
export const REQUIRED_KEYS: ReadonlySet<string> = new Set([
|
|
105
118
|
...WEB_ENV_KEYS.map((k) => k.envVar),
|
|
106
119
|
...DAEMON_ENV_KEYS.map((k) => k.envVar),
|
|
120
|
+
...ROUTER_ENV_KEYS.map((k) => k.envVar),
|
|
107
121
|
]);
|
|
108
122
|
|
|
109
123
|
// Keys whose value should be redacted in `config list` output.
|
|
110
124
|
export const SECRET_ENV_VARS: ReadonlySet<string> = new Set([
|
|
111
125
|
...WEB_ENV_KEYS.filter((k) => k.secret).map((k) => k.envVar),
|
|
112
126
|
...DAEMON_ENV_KEYS.filter((k) => k.secret).map((k) => k.envVar),
|
|
127
|
+
...ROUTER_ENV_KEYS.filter((k) => k.secret).map((k) => k.envVar),
|
|
113
128
|
]);
|