project-mgr 0.2.0 → 0.3.0
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/CHANGELOG.md +17 -0
- package/README.md +22 -1
- package/README_CN.md +1 -1
- package/dist/cli.js +37 -32
- package/dist/db.d.ts +33 -1
- package/dist/db.js +259 -10
- package/dist/domain.d.ts +12 -1
- package/dist/domain.js +9 -0
- package/dist/server.js +19 -2
- package/dist/web/i18n.d.ts +8 -0
- package/dist/web/i18n.js +77 -0
- package/dist/web/locales/en.d.ts +2 -0
- package/dist/web/locales/en.js +52 -0
- package/dist/web/locales/zh.d.ts +54 -0
- package/dist/web/locales/zh.js +52 -0
- package/dist/web/script.d.ts +2 -0
- package/dist/web/script.js +356 -0
- package/dist/web/styles.d.ts +1 -0
- package/dist/web/styles.js +636 -0
- package/dist/web/ui.d.ts +3 -1
- package/dist/web/ui.js +33 -924
- package/package.json +8 -2
- package/skills/project-mgr/SKILL.md +39 -16
- package/.claude/settings.local.json +0 -5
- package/.env_sample +0 -7
- package/.prettierignore +0 -4
- package/AGENTS.md +0 -5
- package/docs/architecture.md +0 -95
- package/eslint.config.js +0 -12
- package/pnpm-workspace.yaml +0 -3
- package/src/cli.ts +0 -399
- package/src/db.ts +0 -373
- package/src/domain.ts +0 -226
- package/src/index.ts +0 -5
- package/src/server.ts +0 -202
- package/src/web/ui.ts +0 -977
- package/test/core.test.ts +0 -739
- package/test/server.test.ts +0 -354
- package/tsconfig.json +0 -14
package/src/server.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
createServer,
|
|
4
|
-
type IncomingMessage,
|
|
5
|
-
type Server,
|
|
6
|
-
type ServerResponse,
|
|
7
|
-
} from "node:http";
|
|
8
|
-
import { URL } from "node:url";
|
|
9
|
-
import {
|
|
10
|
-
countEntries,
|
|
11
|
-
history,
|
|
12
|
-
listProjects,
|
|
13
|
-
openLedger,
|
|
14
|
-
type Ledger,
|
|
15
|
-
} from "./db.js";
|
|
16
|
-
import { getWebUIHTML } from "./web/ui.js";
|
|
17
|
-
|
|
18
|
-
export type ServerOptions = {
|
|
19
|
-
port?: number;
|
|
20
|
-
host?: string;
|
|
21
|
-
open?: boolean;
|
|
22
|
-
db?: Ledger;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type ServerInstance = {
|
|
26
|
-
server: Server;
|
|
27
|
-
url: string;
|
|
28
|
-
port: number;
|
|
29
|
-
host: string;
|
|
30
|
-
close: () => Promise<void>;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export function openBrowser(url: string): void {
|
|
34
|
-
const platform = process.platform;
|
|
35
|
-
let command = "";
|
|
36
|
-
if (platform === "darwin") {
|
|
37
|
-
command = `open "${url}"`;
|
|
38
|
-
} else if (platform === "win32") {
|
|
39
|
-
command = `start "" "${url}"`;
|
|
40
|
-
} else {
|
|
41
|
-
command = `xdg-open "${url}"`;
|
|
42
|
-
}
|
|
43
|
-
exec(command, () => {
|
|
44
|
-
/* ignore background exec errors */
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function parseQueryParams(url: URL): Record<string, string> {
|
|
49
|
-
const params: Record<string, string> = {};
|
|
50
|
-
for (const [key, value] of url.searchParams.entries()) {
|
|
51
|
-
params[key] = value;
|
|
52
|
-
}
|
|
53
|
-
return params;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function sendJson(
|
|
57
|
-
res: ServerResponse,
|
|
58
|
-
statusCode: number,
|
|
59
|
-
data: unknown,
|
|
60
|
-
): void {
|
|
61
|
-
res.writeHead(statusCode, {
|
|
62
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
63
|
-
"Cache-Control": "no-cache",
|
|
64
|
-
});
|
|
65
|
-
res.end(JSON.stringify(data));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function handleRequest(
|
|
69
|
-
req: IncomingMessage,
|
|
70
|
-
res: ServerResponse,
|
|
71
|
-
db: Ledger,
|
|
72
|
-
): void {
|
|
73
|
-
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
74
|
-
sendJson(res, 405, { error: "Method not allowed" });
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const reqUrl = new URL(
|
|
79
|
-
req.url || "/",
|
|
80
|
-
`http://${req.headers.host || "localhost"}`,
|
|
81
|
-
);
|
|
82
|
-
const pathname = reqUrl.pathname;
|
|
83
|
-
const query = parseQueryParams(reqUrl);
|
|
84
|
-
|
|
85
|
-
if (pathname === "/" || pathname === "/index.html") {
|
|
86
|
-
res.writeHead(200, {
|
|
87
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
88
|
-
"Cache-Control": "no-cache",
|
|
89
|
-
});
|
|
90
|
-
res.end(getWebUIHTML());
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (pathname === "/api/projects") {
|
|
95
|
-
const projects = listProjects(db, { query: query.query });
|
|
96
|
-
sendJson(res, 200, projects);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (pathname.startsWith("/api/projects/")) {
|
|
101
|
-
const slug = decodeURIComponent(pathname.slice("/api/projects/".length));
|
|
102
|
-
const projects = listProjects(db);
|
|
103
|
-
const target = projects.find((p) => p.project === slug);
|
|
104
|
-
if (!target) {
|
|
105
|
-
sendJson(res, 404, { error: "Project not found" });
|
|
106
|
-
} else {
|
|
107
|
-
sendJson(res, 200, target);
|
|
108
|
-
}
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (pathname === "/api/history") {
|
|
113
|
-
const project = query.project;
|
|
114
|
-
const since = query.since;
|
|
115
|
-
const until = query.until;
|
|
116
|
-
const rawLimit = Number(query.limit ?? 100);
|
|
117
|
-
const rawOffset = Number(query.offset ?? 0);
|
|
118
|
-
|
|
119
|
-
const entries = history(db, project, {
|
|
120
|
-
since,
|
|
121
|
-
until,
|
|
122
|
-
limit: Number.isInteger(rawLimit) && rawLimit >= 0 ? rawLimit : 100,
|
|
123
|
-
offset: Number.isInteger(rawOffset) && rawOffset >= 0 ? rawOffset : 0,
|
|
124
|
-
});
|
|
125
|
-
sendJson(res, 200, entries);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (pathname === "/api/stats") {
|
|
130
|
-
const projects = listProjects(db);
|
|
131
|
-
const totalEntries = countEntries(db);
|
|
132
|
-
const statusCounts: Record<string, number> = {};
|
|
133
|
-
|
|
134
|
-
for (const proj of projects) {
|
|
135
|
-
const st = proj.latestStatus?.status || "none";
|
|
136
|
-
statusCounts[st] = (statusCounts[st] || 0) + 1;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
sendJson(res, 200, {
|
|
140
|
-
totalProjects: projects.length,
|
|
141
|
-
totalEntries,
|
|
142
|
-
statusCounts,
|
|
143
|
-
});
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
sendJson(res, 404, { error: "Not found" });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function startServer(
|
|
151
|
-
options: ServerOptions = {},
|
|
152
|
-
): Promise<ServerInstance> {
|
|
153
|
-
const host = options.host ?? "127.0.0.1";
|
|
154
|
-
const port =
|
|
155
|
-
options.port ??
|
|
156
|
-
(process.env.PORT ? Number(process.env.PORT) : /* c8 ignore next */ 3000);
|
|
157
|
-
const db = options.db || openLedger();
|
|
158
|
-
const shouldOpen = options.open ?? true;
|
|
159
|
-
|
|
160
|
-
const server = createServer((req, res) => {
|
|
161
|
-
try {
|
|
162
|
-
handleRequest(req, res, db);
|
|
163
|
-
} catch (err) {
|
|
164
|
-
sendJson(res, 500, {
|
|
165
|
-
error: err instanceof Error ? err.message : String(err),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
return new Promise((resolve, reject) => {
|
|
171
|
-
server.once("error", reject);
|
|
172
|
-
server.listen(port, host, () => {
|
|
173
|
-
/* c8 ignore start -- server.address() ternary branch is Node network adapter glue */
|
|
174
|
-
const address = server.address();
|
|
175
|
-
const actualPort =
|
|
176
|
-
typeof address === "object" && address !== null ? address.port : port;
|
|
177
|
-
/* c8 ignore stop */
|
|
178
|
-
const url = `http://${host}:${actualPort}`;
|
|
179
|
-
|
|
180
|
-
if (shouldOpen) {
|
|
181
|
-
openBrowser(url);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
resolve({
|
|
185
|
-
server,
|
|
186
|
-
url,
|
|
187
|
-
port: actualPort,
|
|
188
|
-
host,
|
|
189
|
-
close: () =>
|
|
190
|
-
new Promise<void>((resClose, rejClose) => {
|
|
191
|
-
server.close((err) => {
|
|
192
|
-
if (!options.db) {
|
|
193
|
-
db.close();
|
|
194
|
-
}
|
|
195
|
-
if (err) rejClose(err);
|
|
196
|
-
else resClose();
|
|
197
|
-
});
|
|
198
|
-
}),
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
}
|