jobtrack 1.0.10 → 1.0.11
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 +11 -2
- package/package.json +4 -1
- package/src/cli.ts +67 -2
- package/src/server.ts +25 -1
package/README.md
CHANGED
|
@@ -10,13 +10,22 @@ road with the same company. Single user, runs on your machine, data in a local S
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
**On Windows**, prefer the installer:
|
|
14
|
+
[**JobTrack-Setup**](https://github.com/CuplexUser/JobTrack/releases/latest). It needs no admin
|
|
15
|
+
rights and no Node.js, adds a native tray icon and a settings dialog, and starts silently at
|
|
16
|
+
sign-in rather than opening a console window.
|
|
17
|
+
|
|
18
|
+
Everywhere else — and for development on Windows too:
|
|
19
|
+
|
|
13
20
|
```bash
|
|
14
21
|
npm install -g jobtrack
|
|
15
22
|
jobtrack
|
|
16
23
|
```
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
sources via `tsx` rather than shipping a compiled, dependency-free binary.
|
|
25
|
+
That route requires **Node.js 24+** already installed on the machine, since it runs the app's
|
|
26
|
+
TypeScript sources via `tsx` rather than shipping a compiled, dependency-free binary. Both routes
|
|
27
|
+
use the same `%APPDATA%\jobtrack` data directory, so they share one database — install only one of
|
|
28
|
+
them at a time, or the two will collide on port 3001.
|
|
20
29
|
|
|
21
30
|
## What you get
|
|
22
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jobtrack",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Runs the JobTrack API + web UI as one background process with a Windows tray icon",
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"directory": "apps/tray"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://github.com/CuplexUser/JobTrack#readme",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=24.0.0"
|
|
15
|
+
},
|
|
13
16
|
"main": "./src/cli.ts",
|
|
14
17
|
"bin": {
|
|
15
18
|
"jobtrack": "./bin/jobtrack.js"
|
package/src/cli.ts
CHANGED
|
@@ -4,19 +4,55 @@
|
|
|
4
4
|
* Starts the API + web UI as one process (server.ts) and, on Windows, shows a tray icon for
|
|
5
5
|
* opening the UI, toggling autostart, and opening .env (tray.ts). Elsewhere it just runs
|
|
6
6
|
* headless — Ctrl+C to stop — until a native tray for those platforms is worth building.
|
|
7
|
+
*
|
|
8
|
+
* The Windows installer (see `windows/`) is the one other caller: it draws its own native tray
|
|
9
|
+
* and supervises this process, so it passes `--no-tray` and `--home`, reads the JOBTRACK_READY
|
|
10
|
+
* line below off stdout, and asks for a clean stop by writing `quit` to stdin. None of that
|
|
11
|
+
* changes what plain `jobtrack` does.
|
|
7
12
|
*/
|
|
13
|
+
import { resolve } from 'node:path';
|
|
8
14
|
import { startServer } from './server.js';
|
|
9
|
-
import { createTray } from './tray.js';
|
|
10
15
|
import { isAutostartEnabled, enableAutostart, disableAutostart } from './autostart.js';
|
|
11
16
|
import { openSettingsFile } from './settings.js';
|
|
12
17
|
import { openUrl } from './os.js';
|
|
13
18
|
import { APP_VERSION } from './version.js';
|
|
14
19
|
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
|
|
22
|
+
function flagValue(name: string): string | undefined {
|
|
23
|
+
const index = argv.indexOf(name);
|
|
24
|
+
return index === -1 ? undefined : argv[index + 1];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Before startServer(), and that ordering is the whole point: `resolveAppDataDir()` in
|
|
28
|
+
// apps/api/src/config.ts reads process.env when it's called, and everything under the data
|
|
29
|
+
// directory — the database, the model cache, .env itself — hangs off what it returns.
|
|
30
|
+
//
|
|
31
|
+
// A flag rather than an inherited JOBTRACK_HOME because bin/jobtrack.js documents that handing
|
|
32
|
+
// a reconstructed environment to a Node child breaks tsx's loader; a supervisor that never has
|
|
33
|
+
// to set an environment variable can't trip over that at all.
|
|
34
|
+
const home = flagValue('--home');
|
|
35
|
+
if (home) process.env.JOBTRACK_HOME = resolve(home);
|
|
36
|
+
|
|
37
|
+
const noTray = argv.includes('--no-tray') || process.env.JOBTRACK_NO_TRAY === '1';
|
|
38
|
+
|
|
15
39
|
const { app, config, repos, search } = await startServer();
|
|
16
40
|
|
|
17
41
|
const url = `http://${config.host === '0.0.0.0' ? '127.0.0.1' : config.host}:${config.port}`;
|
|
18
42
|
console.log(`JobTrack v${APP_VERSION} running at ${url} (driver: ${config.driver})`);
|
|
19
43
|
|
|
44
|
+
// The same facts again, in one parseable line, so a supervisor knows the server is up and on
|
|
45
|
+
// which address without scraping prose or guessing at a port it may have only set in .env.
|
|
46
|
+
console.log(
|
|
47
|
+
`JOBTRACK_READY ${JSON.stringify({
|
|
48
|
+
url,
|
|
49
|
+
host: config.host,
|
|
50
|
+
port: config.port,
|
|
51
|
+
version: APP_VERSION,
|
|
52
|
+
driver: config.driver,
|
|
53
|
+
})}`,
|
|
54
|
+
);
|
|
55
|
+
|
|
20
56
|
let shuttingDown = false;
|
|
21
57
|
async function shutdown(): Promise<void> {
|
|
22
58
|
if (shuttingDown) return;
|
|
@@ -31,7 +67,36 @@ for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
|
|
31
67
|
process.once(signal, () => void shutdown());
|
|
32
68
|
}
|
|
33
69
|
|
|
34
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Line-oriented commands from a supervising process. Windows has no dependable way to send a
|
|
72
|
+
* running process anything like SIGTERM from outside, so stdin is how the installer's host asks
|
|
73
|
+
* for the orderly shutdown above — closing SQLite properly — before it resorts to killing the
|
|
74
|
+
* job object.
|
|
75
|
+
*/
|
|
76
|
+
function listenForSupervisorCommands(): void {
|
|
77
|
+
process.stdin.setEncoding('utf8');
|
|
78
|
+
let buffered = '';
|
|
79
|
+
process.stdin.on('data', (chunk: string) => {
|
|
80
|
+
buffered += chunk;
|
|
81
|
+
let newline: number;
|
|
82
|
+
while ((newline = buffered.indexOf('\n')) !== -1) {
|
|
83
|
+
const line = buffered.slice(0, newline).trim();
|
|
84
|
+
buffered = buffered.slice(newline + 1);
|
|
85
|
+
if (line === 'quit') void shutdown();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// The supervisor is gone; there is nobody left to serve.
|
|
89
|
+
process.stdin.on('end', () => void shutdown());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (noTray) {
|
|
93
|
+
listenForSupervisorCommands();
|
|
94
|
+
} else if (process.platform === 'win32') {
|
|
95
|
+
// Imported here rather than at the top so that `systray` — 35 MB of Go tray binaries for
|
|
96
|
+
// three platforms — is only ever loaded when a tray is actually going to be drawn. That is
|
|
97
|
+
// what lets the Windows installer's payload delete the package outright: its native host
|
|
98
|
+
// draws the tray itself and always passes --no-tray.
|
|
99
|
+
const { createTray } = await import('./tray.js');
|
|
35
100
|
const tray = createTray({
|
|
36
101
|
autostartEnabled: isAutostartEnabled(),
|
|
37
102
|
onOpen: () => openUrl(url),
|
package/src/server.ts
CHANGED
|
@@ -17,6 +17,12 @@ import { TransformersEmbedder } from '@jobtrack/api/search/transformers-embedder
|
|
|
17
17
|
import { resolveWebDist } from './assets.js';
|
|
18
18
|
import { APP_PACKAGE } from './version.js';
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Exit code for "the port is taken". Distinct from a generic crash so a supervisor can tell a
|
|
22
|
+
* misconfiguration it should surface to the user from a fault it should just retry.
|
|
23
|
+
*/
|
|
24
|
+
export const EXIT_PORT_IN_USE = 3;
|
|
25
|
+
|
|
20
26
|
export interface RunningServer {
|
|
21
27
|
app: FastifyInstance;
|
|
22
28
|
config: Config;
|
|
@@ -68,7 +74,25 @@ export async function startServer(): Promise<RunningServer> {
|
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
await search.start();
|
|
71
|
-
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await app.listen({ host: config.host, port: config.port });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
// Overwhelmingly this is a second JobTrack — autostart plus a manual launch. Left alone it
|
|
82
|
+
// surfaces as an unhandled rejection and a stack trace; a supervisor (and a person) can do
|
|
83
|
+
// something useful with a named cause and a distinct exit code.
|
|
84
|
+
if ((error as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw error;
|
|
85
|
+
console.error(
|
|
86
|
+
`JOBTRACK_ERROR ${JSON.stringify({ code: 'EADDRINUSE', host: config.host, port: config.port })}`,
|
|
87
|
+
);
|
|
88
|
+
console.error(
|
|
89
|
+
`Port ${config.port} is already in use — JobTrack may already be running. Stop it, or set PORT in .env to something else.`,
|
|
90
|
+
);
|
|
91
|
+
search.stop();
|
|
92
|
+
await app.close();
|
|
93
|
+
await repos.close();
|
|
94
|
+
process.exit(EXIT_PORT_IN_USE);
|
|
95
|
+
}
|
|
72
96
|
|
|
73
97
|
return { app, config, repos, search };
|
|
74
98
|
}
|