ework-aio 0.2.3 → 0.2.5
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/cli.ts +1 -1
- package/src/commands/install.ts +22 -7
- package/src/commands/lifecycle.ts +5 -1
- package/src/opencode-config.ts +67 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ework-aio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
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/cli.ts
CHANGED
package/src/commands/install.ts
CHANGED
|
@@ -97,20 +97,35 @@ export async function runInstall(
|
|
|
97
97
|
logger.ok(`preflight: bun, npm, opencode all on PATH`);
|
|
98
98
|
|
|
99
99
|
// 2. Resolve ework-web / ework-daemon binaries (npm-installed global bins).
|
|
100
|
+
//
|
|
101
|
+
// ework-daemon ships TWO bins: `ework-daemon` (client CLI: status/issues/...)
|
|
102
|
+
// and `ework-daemon-server` (actual HTTP server). We start the SERVER, not
|
|
103
|
+
// the client — pre-v0.2.5 we spawned the client, which prints help and
|
|
104
|
+
// exits, leaving the daemon "looking dead" with a help-text log.
|
|
100
105
|
const webBin = resolveCommand("ework-web");
|
|
101
|
-
const
|
|
106
|
+
const daemonClientBin = resolveCommand("ework-daemon");
|
|
107
|
+
const daemonServerBin = resolveCommand("ework-daemon-server");
|
|
102
108
|
if (!webBin) {
|
|
103
109
|
throw new InstallError(
|
|
104
110
|
`ework-web binary not found on PATH. Install with: npm install -g ework-web`,
|
|
105
111
|
);
|
|
106
112
|
}
|
|
107
|
-
if (!
|
|
113
|
+
if (!daemonClientBin && !daemonServerBin) {
|
|
114
|
+
throw new InstallError(
|
|
115
|
+
`ework-daemon not installed. Install with: npm install -g ework-daemon`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (!daemonServerBin) {
|
|
119
|
+
// Client CLI present but server bin missing — old / partial install.
|
|
108
120
|
throw new InstallError(
|
|
109
|
-
`ework-daemon binary not found on PATH
|
|
121
|
+
`ework-daemon-server binary not found on PATH, but ework-daemon (client) is present. ` +
|
|
122
|
+
`Your ework-daemon npm install is incomplete or outdated. Reinstall with: ` +
|
|
123
|
+
`npm install -g ework-daemon@latest`,
|
|
110
124
|
);
|
|
111
125
|
}
|
|
112
|
-
logger.ok(`web bin
|
|
113
|
-
logger.ok(`daemon
|
|
126
|
+
logger.ok(`web bin : ${webBin}`);
|
|
127
|
+
logger.ok(`daemon client : ${daemonClientBin ?? "(not on PATH)"}`);
|
|
128
|
+
logger.ok(`daemon server : ${daemonServerBin}`);
|
|
114
129
|
|
|
115
130
|
// 3. Resolve all filesystem paths.
|
|
116
131
|
const paths = resolvePaths({
|
|
@@ -349,7 +364,7 @@ export async function runInstall(
|
|
|
349
364
|
user: userInfo.username,
|
|
350
365
|
group: userInfo.username,
|
|
351
366
|
binPath: preflight.found.get("bun")!,
|
|
352
|
-
mainScript:
|
|
367
|
+
mainScript: daemonServerBin,
|
|
353
368
|
envFile: paths.daemonEnvFile,
|
|
354
369
|
workingDirectory: paths.daemonDataDir,
|
|
355
370
|
logFile: paths.daemonLogFile,
|
|
@@ -388,7 +403,7 @@ export async function runInstall(
|
|
|
388
403
|
logger.log(`starting ework-daemon (PID-file mode)...`);
|
|
389
404
|
const env = await loadEnvIntoProcess(paths.daemonEnvFile);
|
|
390
405
|
const { pid } = await startProcess({
|
|
391
|
-
cmd:
|
|
406
|
+
cmd: daemonServerBin,
|
|
392
407
|
args: [],
|
|
393
408
|
cwd: paths.daemonDataDir,
|
|
394
409
|
env,
|
|
@@ -41,7 +41,11 @@ function servicePaths(paths: PathConfig, svc: "web" | "daemon"): ServicePaths {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
return {
|
|
44
|
-
|
|
44
|
+
// ework-daemon ships two bins; only `ework-daemon-server` actually starts
|
|
45
|
+
// the HTTP server. The `ework-daemon` bin is a client CLI that prints help
|
|
46
|
+
// and exits — spawning it leaves the daemon "looking dead" with help text
|
|
47
|
+
// in the log. See install.ts preflight for the full rationale.
|
|
48
|
+
bin: "ework-daemon-server",
|
|
45
49
|
dataDir: paths.daemonDataDir,
|
|
46
50
|
envFile: paths.daemonEnvFile,
|
|
47
51
|
pidFile: paths.daemonPidFile,
|
package/src/opencode-config.ts
CHANGED
|
@@ -6,10 +6,30 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { InstallError } from "./log.ts";
|
|
8
8
|
|
|
9
|
+
// Per https://opencode.ai/config.json (verified v1.14.x), the top-level
|
|
10
|
+
// schema key is "plugin" (singular), an array. Each entry is either a
|
|
11
|
+
// string ("opencode-ework@latest") or a 2-tuple [name, options]. The
|
|
12
|
+
// schema sets additionalProperties: false at top level, so any other key
|
|
13
|
+
// name — including the plausible-looking "plugins" plural — is rejected
|
|
14
|
+
// with "Unrecognized key" and breaks every opencode command that parses
|
|
15
|
+
// config (export, session list, etc).
|
|
16
|
+
//
|
|
17
|
+
// v0.2.0-v0.2.3 wrote the WRONG key ("plugins"). On the user's machine
|
|
18
|
+
// the result was: install completes, but clicking any session in
|
|
19
|
+
// awork-web fails because awork-web calls `opencode export ses_xxx`,
|
|
20
|
+
// opencode parses its config first, and aborts with "Unrecognized key:
|
|
21
|
+
// plugins". This file now writes the correct key, and the reader
|
|
22
|
+
// transparently migrates any legacy "plugins" array into "plugin" so
|
|
23
|
+
// reinstall fixes existing broken configs without manual editing.
|
|
9
24
|
export interface OpencodeConfig {
|
|
10
|
-
// Unknown-key passthrough — preserves fields we don't know about.
|
|
11
25
|
[key: string]: unknown;
|
|
12
|
-
|
|
26
|
+
// Accepted entry shapes (we are lenient on read):
|
|
27
|
+
// - "name@version" (opencode schema — canonical)
|
|
28
|
+
// - ["name", {...opts}] (opencode schema — 2-tuple form)
|
|
29
|
+
// - { name: "..." } (legacy from earlier ework-aio; opencode
|
|
30
|
+
// rejects this, but we preserve through
|
|
31
|
+
// read+write so users don't lose data)
|
|
32
|
+
plugin?: Array<string | [string, unknown] | { name: string; [k: string]: unknown }>;
|
|
13
33
|
}
|
|
14
34
|
|
|
15
35
|
export interface ReadConfigResult {
|
|
@@ -43,7 +63,7 @@ export async function readConfig(configPath: string): Promise<ReadConfigResult>
|
|
|
43
63
|
);
|
|
44
64
|
}
|
|
45
65
|
|
|
46
|
-
return { config: parsed as OpencodeConfig, existed: true };
|
|
66
|
+
return { config: migrateLegacyPluginsKey(parsed as OpencodeConfig), existed: true };
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
export async function writeConfig(configPath: string, config: OpencodeConfig): Promise<void> {
|
|
@@ -60,9 +80,42 @@ export async function writeConfig(configPath: string, config: OpencodeConfig): P
|
|
|
60
80
|
}
|
|
61
81
|
}
|
|
62
82
|
|
|
83
|
+
// migrateLegacyPluginsKey: if a config has the v0.2.0-v0.2.3 "plugins"
|
|
84
|
+
// (plural) key from our earlier broken installer, merge its contents
|
|
85
|
+
// into the correct "plugin" key and drop the bad key. Idempotent — if
|
|
86
|
+
// the user re-runs install with the fixed version, their config gets
|
|
87
|
+
// healed automatically. Returns the original config object untouched if
|
|
88
|
+
// no migration was needed.
|
|
89
|
+
function migrateLegacyPluginsKey(config: OpencodeConfig): OpencodeConfig {
|
|
90
|
+
const legacy = config.plugins;
|
|
91
|
+
if (!Array.isArray(legacy)) {
|
|
92
|
+
// Either no legacy key, or it's malformed (not an array). Delete
|
|
93
|
+
// either way so we don't persist an unrecognized key back to disk.
|
|
94
|
+
if ("plugins" in config) {
|
|
95
|
+
const next = { ...config };
|
|
96
|
+
delete next.plugins;
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
return config;
|
|
100
|
+
}
|
|
101
|
+
const next: OpencodeConfig = { ...config };
|
|
102
|
+
delete next.plugins;
|
|
103
|
+
const canonical = Array.isArray(config.plugin) ? [...config.plugin] : [];
|
|
104
|
+
for (const entry of legacy) {
|
|
105
|
+
const name = normalizePluginEntry(entry);
|
|
106
|
+
if (!name) continue;
|
|
107
|
+
const already = canonical.some((e) => normalizePluginEntry(e) === name);
|
|
108
|
+
if (!already) canonical.push(entry);
|
|
109
|
+
}
|
|
110
|
+
next.plugin = canonical;
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
113
|
+
|
|
63
114
|
function normalizePluginEntry(entry: unknown): string | null {
|
|
64
|
-
// Plugin entries can be either "name" (string) or { name: "...", ...opts }.
|
|
65
115
|
if (typeof entry === "string") return entry;
|
|
116
|
+
if (Array.isArray(entry) && entry.length >= 1 && typeof entry[0] === "string") {
|
|
117
|
+
return entry[0];
|
|
118
|
+
}
|
|
66
119
|
if (entry !== null && typeof entry === "object") {
|
|
67
120
|
const name = (entry as { name?: unknown }).name;
|
|
68
121
|
if (typeof name === "string") return name;
|
|
@@ -71,9 +124,9 @@ function normalizePluginEntry(entry: unknown): string | null {
|
|
|
71
124
|
}
|
|
72
125
|
|
|
73
126
|
export function hasPlugin(config: OpencodeConfig, pluginName: string): boolean {
|
|
74
|
-
const
|
|
75
|
-
if (!Array.isArray(
|
|
76
|
-
return
|
|
127
|
+
const list = config.plugin;
|
|
128
|
+
if (!Array.isArray(list)) return false;
|
|
129
|
+
return list.some((p) => normalizePluginEntry(p) === pluginName);
|
|
77
130
|
}
|
|
78
131
|
|
|
79
132
|
export function ensurePlugin(config: OpencodeConfig, pluginName: string): { config: OpencodeConfig; added: boolean } {
|
|
@@ -81,7 +134,7 @@ export function ensurePlugin(config: OpencodeConfig, pluginName: string): { conf
|
|
|
81
134
|
return { config, added: false };
|
|
82
135
|
}
|
|
83
136
|
const next: OpencodeConfig = { ...config };
|
|
84
|
-
next.
|
|
137
|
+
next.plugin = [...(Array.isArray(config.plugin) ? config.plugin : []), pluginName];
|
|
85
138
|
return { config: next, added: true };
|
|
86
139
|
}
|
|
87
140
|
|
|
@@ -90,15 +143,17 @@ export function removePlugin(config: OpencodeConfig, pluginName: string): { conf
|
|
|
90
143
|
return { config, removed: false };
|
|
91
144
|
}
|
|
92
145
|
const next: OpencodeConfig = { ...config };
|
|
93
|
-
const
|
|
94
|
-
next.
|
|
146
|
+
const list = Array.isArray(config.plugin) ? config.plugin : [];
|
|
147
|
+
next.plugin = list.filter((p) => normalizePluginEntry(p) !== pluginName);
|
|
95
148
|
return { config: next, removed: true };
|
|
96
149
|
}
|
|
97
150
|
|
|
98
151
|
export async function ensurePluginInFile(configPath: string, pluginName: string): Promise<{ added: boolean }> {
|
|
99
152
|
const { config } = await readConfig(configPath);
|
|
100
153
|
const result = ensurePlugin(config, pluginName);
|
|
101
|
-
|
|
154
|
+
// Always write — readConfig may have migrated a legacy "plugins" key,
|
|
155
|
+
// and we want to persist the corrected shape to disk even if the
|
|
156
|
+
// plugin was already present under the (now-renamed) "plugin" key.
|
|
102
157
|
await writeConfig(configPath, result.config);
|
|
103
|
-
return { added:
|
|
158
|
+
return { added: result.added };
|
|
104
159
|
}
|