ework-aio 0.3.3 → 0.3.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 +5 -1
- package/src/commands/install.ts +3 -1
- package/src/commands/uninstall.ts +19 -1
- package/src/preflight.ts +45 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ework-aio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// - Unknown flags → error. We never silently swallow typos.
|
|
13
13
|
|
|
14
14
|
import os from "node:os";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
15
16
|
import { spawnSync } from "node:child_process";
|
|
16
17
|
import { Logger, InstallError, log as defaultLogger } from "./log.ts";
|
|
17
18
|
import {
|
|
@@ -35,7 +36,10 @@ import {
|
|
|
35
36
|
type ConfigArgs,
|
|
36
37
|
} from "./commands/config.ts";
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
// Read from package.json — never hardcode (every release reported "0.2.7-dev").
|
|
40
|
+
const VERSION: string = JSON.parse(
|
|
41
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf-8"),
|
|
42
|
+
).version;
|
|
39
43
|
|
|
40
44
|
const USAGE = `ework-aio <command> [options]
|
|
41
45
|
|
package/src/commands/install.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { resolvePaths, type PathConfig } from "../paths.ts";
|
|
|
24
24
|
import { type InstallContext } from "../config.ts";
|
|
25
25
|
import { ensureEnvFile, parseEnvFile, patchEnvKey } from "../env.ts";
|
|
26
26
|
import { startProcess, isProcessRunning, readPidFile } from "../pidfile.ts";
|
|
27
|
-
import { checkPreflight, resolveCommand, resolveBundledBin, REQUIRED_COMMANDS } from "../preflight.ts";
|
|
27
|
+
import { checkPreflight, resolveCommand, resolveBundledBin, ensureSelfBinSymlink, REQUIRED_COMMANDS } from "../preflight.ts";
|
|
28
28
|
import {
|
|
29
29
|
generateUnitFile,
|
|
30
30
|
writeUnitFile,
|
|
@@ -96,6 +96,8 @@ export async function runInstall(
|
|
|
96
96
|
const opencodeBin = preflight.found.get("opencode")!;
|
|
97
97
|
logger.ok(`preflight: bun, npm, opencode all on PATH`);
|
|
98
98
|
|
|
99
|
+
ensureSelfBinSymlink(logger);
|
|
100
|
+
|
|
99
101
|
// 2. Resolve ework-web / ework-daemon binaries.
|
|
100
102
|
//
|
|
101
103
|
// ework-daemon ships TWO bins: `ework-daemon` (client CLI: status/issues/...)
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// all user data. Idempotent — missing units or stale PID files are not
|
|
3
3
|
// errors. Data dir is never touched; user removes it manually if desired.
|
|
4
4
|
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
5
7
|
import { Logger } from "../log.ts";
|
|
6
8
|
import { resolvePaths } from "../paths.ts";
|
|
7
9
|
import { stopProcess } from "../pidfile.ts";
|
|
@@ -58,7 +60,23 @@ export async function runUninstall(opts: GlobalOptions, logger: Logger): Promise
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// 3. Print recovery hint.
|
|
63
|
+
// Derive ework-aio's own install path from import.meta.dir so the removal
|
|
64
|
+
// command works even when npm's global prefix changed since install (the
|
|
65
|
+
// bin symlink may live under a different prefix than `npm prefix -g`).
|
|
66
|
+
const pkgRoot = path.resolve(import.meta.dir, "..", "..");
|
|
67
|
+
const binCandidates = [
|
|
68
|
+
path.join(pkgRoot, "..", "..", "..", "bin", "ework-aio"),
|
|
69
|
+
path.join(pkgRoot, "..", "..", "bin", "ework-aio"),
|
|
70
|
+
];
|
|
71
|
+
const binPath = binCandidates.find((p) => existsSync(p));
|
|
72
|
+
const rmSelf = `rm -rf ${pkgRoot}${binPath ? ` ${binPath}` : ""}`;
|
|
73
|
+
|
|
61
74
|
logger.hr();
|
|
62
75
|
logger.ok(`services removed. data preserved at ${paths.dataDir}`);
|
|
63
|
-
logger.warn(
|
|
76
|
+
logger.warn(
|
|
77
|
+
`to fully remove:\n` +
|
|
78
|
+
` rm -rf ${paths.dataDir}\n` +
|
|
79
|
+
` ${rmSelf}\n` +
|
|
80
|
+
` npm uninstall -g ework-web ework-daemon opencode-ework`,
|
|
81
|
+
);
|
|
64
82
|
}
|
package/src/preflight.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import type { Logger } from "./log.ts";
|
|
4
5
|
|
|
5
6
|
// Root of the ework-aio package itself. Derived from this file's location
|
|
6
7
|
// (<root>/src/preflight.ts). Used to resolve bins that ework-aio ships as
|
|
@@ -68,3 +69,47 @@ export function checkPreflight(
|
|
|
68
69
|
|
|
69
70
|
export const REQUIRED_COMMANDS: readonly string[] = ["bun", "npm", "opencode"];
|
|
70
71
|
export const OPTIONAL_COMMANDS: readonly string[] = ["systemctl", "sudo"];
|
|
72
|
+
|
|
73
|
+
// Ensure `ework-aio` is reachable from PATH. npm puts the bin at its own
|
|
74
|
+
// global prefix's bin dir, which may differ from every dir on PATH (e.g.
|
|
75
|
+
// prefix changed after a node upgrade). Walk PATH in order and, at the
|
|
76
|
+
// first writable dir, create/repair a symlink to our actual bin.
|
|
77
|
+
export function ensureSelfBinSymlink(logger: Logger): void {
|
|
78
|
+
const ourBin = path.resolve(import.meta.dir, "..", "bin", "ework-aio");
|
|
79
|
+
if (!fs.existsSync(ourBin)) return;
|
|
80
|
+
const ourBinReal = fs.realpathSync(ourBin);
|
|
81
|
+
|
|
82
|
+
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
83
|
+
for (const dir of pathDirs) {
|
|
84
|
+
try {
|
|
85
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const target = path.join(dir, "ework-aio");
|
|
91
|
+
try {
|
|
92
|
+
const stat = fs.lstatSync(target);
|
|
93
|
+
if (stat.isSymbolicLink()) {
|
|
94
|
+
try {
|
|
95
|
+
if (fs.realpathSync(target) === ourBinReal) return;
|
|
96
|
+
} catch {
|
|
97
|
+
// broken symlink — fall through to replace
|
|
98
|
+
}
|
|
99
|
+
fs.unlinkSync(target);
|
|
100
|
+
} else {
|
|
101
|
+
continue; // regular file — don't clobber
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// doesn't exist — fall through to create
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
fs.symlinkSync(ourBin, target);
|
|
109
|
+
logger.ok(`bin symlink: ${target} → ${ourBin}`);
|
|
110
|
+
return;
|
|
111
|
+
} catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|