@x12i/static-memorix-explorer-api 1.4.0 → 1.5.1
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 +22 -0
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +125 -3
- package/guides/demo-app.md +3 -3
- package/guides/running-the-service.md +18 -3
- package/mocks/data/users/snapshots.json +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,16 @@ memorix-explorer --help
|
|
|
49
49
|
memorix-explorer --version
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
Stop a server started by the CLI:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
memorix-explorer stop # port 5030
|
|
56
|
+
memorix-explorer stop --port 8080
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If a requested port is occupied, the CLI explains these stop and alternate-port
|
|
60
|
+
options instead of returning only `EADDRINUSE`.
|
|
61
|
+
|
|
52
62
|
### Run from this repository
|
|
53
63
|
|
|
54
64
|
```bash
|
|
@@ -319,6 +329,18 @@ and `knowledgeId`.
|
|
|
319
329
|
|
|
320
330
|
## Release history
|
|
321
331
|
|
|
332
|
+
### v1.5.1
|
|
333
|
+
- Replaced personal names in the bundled demo users with generic sample names.
|
|
334
|
+
- Added regression coverage to keep personal identifiers out of generated demos.
|
|
335
|
+
|
|
336
|
+
### v1.5.0
|
|
337
|
+
- Added `memorix-explorer stop [--port <number>]` with safe managed-process
|
|
338
|
+
tracking and stale-PID cleanup.
|
|
339
|
+
- Added actionable port-conflict output with stop and alternate-port commands.
|
|
340
|
+
- Changed `create-demo` instructions to relative, user-owned paths such as
|
|
341
|
+
`./memorix-demo`; package or maintainer filesystem paths are never exposed.
|
|
342
|
+
- Added lifecycle and occupied-port integration coverage.
|
|
343
|
+
|
|
322
344
|
### v1.4.0
|
|
323
345
|
- Added `memorix-explorer create-demo` to create a user-owned, writable demo.
|
|
324
346
|
- Added `--dir` for the generated demo destination and `--mocks-dir` for
|
package/dist/cli.d.ts
CHANGED
|
@@ -4,11 +4,14 @@ export interface CliServerOptions {
|
|
|
4
4
|
host?: string;
|
|
5
5
|
mocksDir?: string;
|
|
6
6
|
}
|
|
7
|
-
export declare const CLI_HELP = "Memorix Explorer static server\n\nUsage:\n memorix-explorer create-demo [--dir <path>]\n memorix-explorer [options]\n\nCommands:\n create-demo Create a writable demo folder (default: ./memorix-demo)\n\nServer options:\n --port <number> Port to listen on (default: 5030)\n --host <address> Host to bind (default: 0.0.0.0)\n --mocks-dir <path> JSON fixture directory\n -h, --help Show this help\n -v, --version Show the installed version\n\nQuick start:\n memorix-explorer create-demo\n memorix-explorer --mocks-dir ./memorix-demo\n";
|
|
7
|
+
export declare const CLI_HELP = "Memorix Explorer static server\n\nUsage:\n memorix-explorer create-demo [--dir <path>]\n memorix-explorer stop [--port <number>]\n memorix-explorer [options]\n\nCommands:\n create-demo Create a writable demo folder (default: ./memorix-demo)\n stop Stop a managed server (default port: 5030)\n\nServer options:\n --port <number> Port to listen on (default: 5030)\n --host <address> Host to bind (default: 0.0.0.0)\n --mocks-dir <path> JSON fixture directory\n -h, --help Show this help\n -v, --version Show the installed version\n\nQuick start:\n memorix-explorer create-demo\n memorix-explorer --mocks-dir ./memorix-demo\n";
|
|
8
8
|
export declare function getPackageVersion(): string;
|
|
9
9
|
export declare function parseServerArgs(args: string[]): CliServerOptions;
|
|
10
10
|
export declare function parseCreateDemoArgs(args: string[]): {
|
|
11
11
|
targetDir: string;
|
|
12
12
|
};
|
|
13
13
|
export declare function createDemo(targetDir: string): string;
|
|
14
|
+
export declare function pidFileForPort(port: number): string;
|
|
15
|
+
export declare function registerManagedServer(port: number): void;
|
|
16
|
+
export declare function stopManagedServer(port: number): Promise<boolean>;
|
|
14
17
|
export declare function runCli(args: string[]): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import os from "node:os";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
6
8
|
export const CLI_HELP = `Memorix Explorer static server
|
|
7
9
|
|
|
8
10
|
Usage:
|
|
9
11
|
memorix-explorer create-demo [--dir <path>]
|
|
12
|
+
memorix-explorer stop [--port <number>]
|
|
10
13
|
memorix-explorer [options]
|
|
11
14
|
|
|
12
15
|
Commands:
|
|
13
16
|
create-demo Create a writable demo folder (default: ./memorix-demo)
|
|
17
|
+
stop Stop a managed server (default port: 5030)
|
|
14
18
|
|
|
15
19
|
Server options:
|
|
16
20
|
--port <number> Port to listen on (default: 5030)
|
|
@@ -110,6 +114,102 @@ export function createDemo(targetDir) {
|
|
|
110
114
|
function quote(value) {
|
|
111
115
|
return `"${value.replaceAll('"', '\\"')}"`;
|
|
112
116
|
}
|
|
117
|
+
function displayPath(value) {
|
|
118
|
+
const relative = path.relative(process.cwd(), value);
|
|
119
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
120
|
+
return `./${relative}`;
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function parsePortOption(args) {
|
|
125
|
+
if (args.length === 0)
|
|
126
|
+
return 5030;
|
|
127
|
+
if (args.length !== 2 || args[0] !== "--port") {
|
|
128
|
+
throw new Error("Usage: memorix-explorer stop [--port <number>]");
|
|
129
|
+
}
|
|
130
|
+
const port = Number(args[1]);
|
|
131
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
132
|
+
throw new Error("--port must be an integer from 1 to 65535");
|
|
133
|
+
}
|
|
134
|
+
return port;
|
|
135
|
+
}
|
|
136
|
+
function runtimeDirectory() {
|
|
137
|
+
const user = typeof process.getuid === "function" ? String(process.getuid()) : os.userInfo().username;
|
|
138
|
+
return path.join(os.tmpdir(), `memorix-explorer-${user}`);
|
|
139
|
+
}
|
|
140
|
+
export function pidFileForPort(port) {
|
|
141
|
+
return path.join(runtimeDirectory(), `${port}.pid.json`);
|
|
142
|
+
}
|
|
143
|
+
function processCommand(pid) {
|
|
144
|
+
if (process.platform === "win32")
|
|
145
|
+
return "memorix-explorer";
|
|
146
|
+
try {
|
|
147
|
+
return execFileSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
150
|
+
}).trim();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isRunning(pid) {
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 0);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export function registerManagedServer(port) {
|
|
166
|
+
const file = pidFileForPort(port);
|
|
167
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
168
|
+
fs.writeFileSync(file, JSON.stringify({ pid: process.pid, port, startedAt: new Date().toISOString() }) + "\n");
|
|
169
|
+
process.once("exit", () => {
|
|
170
|
+
try {
|
|
171
|
+
const current = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
172
|
+
if (current.pid === process.pid)
|
|
173
|
+
fs.unlinkSync(file);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Missing or replaced PID files need no cleanup.
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export async function stopManagedServer(port) {
|
|
181
|
+
const file = pidFileForPort(port);
|
|
182
|
+
if (!fs.existsSync(file))
|
|
183
|
+
return false;
|
|
184
|
+
let pid;
|
|
185
|
+
try {
|
|
186
|
+
pid = Number(JSON.parse(fs.readFileSync(file, "utf8")).pid);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
fs.unlinkSync(file);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
if (!Number.isInteger(pid) || pid <= 0 || !isRunning(pid)) {
|
|
193
|
+
fs.unlinkSync(file);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const command = processCommand(pid);
|
|
197
|
+
if (process.platform !== "win32" && !command) {
|
|
198
|
+
throw new Error(`Cannot verify Memorix Explorer PID ${pid}; refusing to stop it`);
|
|
199
|
+
}
|
|
200
|
+
if (command && !/memorix-explorer|static-memorix-explorer-api|mock-memorix-explorer-api|dist\/cli\.js/.test(command)) {
|
|
201
|
+
throw new Error(`Refusing to stop PID ${pid}; it is not a Memorix Explorer process`);
|
|
202
|
+
}
|
|
203
|
+
process.kill(pid, "SIGTERM");
|
|
204
|
+
for (let attempt = 0; attempt < 50 && isRunning(pid); attempt++) {
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
206
|
+
}
|
|
207
|
+
if (isRunning(pid))
|
|
208
|
+
throw new Error(`Timed out while stopping Memorix Explorer PID ${pid}`);
|
|
209
|
+
if (fs.existsSync(file))
|
|
210
|
+
fs.unlinkSync(file);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
113
213
|
export async function runCli(args) {
|
|
114
214
|
if (args.includes("--help") || args.includes("-h")) {
|
|
115
215
|
console.log(CLI_HELP);
|
|
@@ -122,19 +222,41 @@ export async function runCli(args) {
|
|
|
122
222
|
if (args[0] === "create-demo") {
|
|
123
223
|
const { targetDir } = parseCreateDemoArgs(args.slice(1));
|
|
124
224
|
const created = createDemo(targetDir);
|
|
125
|
-
|
|
225
|
+
const shown = displayPath(created);
|
|
226
|
+
console.log(`\nDemo created:\n ${shown}\n`);
|
|
126
227
|
console.log("Start the server with:");
|
|
127
|
-
console.log(` memorix-explorer --mocks-dir ${quote(
|
|
228
|
+
console.log(` memorix-explorer --mocks-dir ${quote(shown)}\n`);
|
|
128
229
|
console.log("Then open:");
|
|
129
230
|
console.log(" http://localhost:5030/demo\n");
|
|
130
231
|
console.log("Create, edit, and delete operations will update JSON files in that folder.");
|
|
131
232
|
return;
|
|
132
233
|
}
|
|
234
|
+
if (args[0] === "stop") {
|
|
235
|
+
const port = parsePortOption(args.slice(1));
|
|
236
|
+
const stopped = await stopManagedServer(port);
|
|
237
|
+
console.log(stopped
|
|
238
|
+
? `Memorix Explorer on port ${port} stopped.`
|
|
239
|
+
: `No managed Memorix Explorer server found on port ${port}.`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
133
242
|
const options = parseServerArgs(args);
|
|
134
243
|
if (options.mocksDir)
|
|
135
244
|
process.env.MOCKS_DIR = options.mocksDir;
|
|
136
245
|
const { startServer } = await import("./server.js");
|
|
137
|
-
|
|
246
|
+
const port = options.port ?? 5030;
|
|
247
|
+
try {
|
|
248
|
+
const app = await startServer({ port, host: options.host });
|
|
249
|
+
const address = app.server.address();
|
|
250
|
+
const boundPort = typeof address === "object" && address ? address.port : port;
|
|
251
|
+
registerManagedServer(boundPort);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (error.code === "EADDRINUSE") {
|
|
255
|
+
throw new Error(`Port ${port} is already in use. Run "memorix-explorer stop --port ${port}" ` +
|
|
256
|
+
`or choose another port with "--port ${port + 1}".`);
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
138
260
|
}
|
|
139
261
|
function isCliEntrypoint() {
|
|
140
262
|
if (!process.argv[1])
|
package/guides/demo-app.md
CHANGED
|
@@ -219,9 +219,9 @@ Data files (under `mocks/data/`) hold the actual records.
|
|
|
219
219
|
|
|
220
220
|
```json
|
|
221
221
|
[
|
|
222
|
-
{ "recordId": "usr-1", "name": "
|
|
223
|
-
{ "recordId": "usr-2", "name": "
|
|
224
|
-
{ "recordId": "usr-3", "name": "
|
|
222
|
+
{ "recordId": "usr-1", "name": "James", "role": "owner" },
|
|
223
|
+
{ "recordId": "usr-2", "name": "Emily", "role": "engineer" },
|
|
224
|
+
{ "recordId": "usr-3", "name": "Michael", "role": "growth" }
|
|
225
225
|
]
|
|
226
226
|
```
|
|
227
227
|
|
|
@@ -71,11 +71,11 @@ memorix-explorer create-demo
|
|
|
71
71
|
The command creates `./memorix-demo` and prints the exact next step:
|
|
72
72
|
|
|
73
73
|
```text
|
|
74
|
-
Demo created
|
|
75
|
-
|
|
74
|
+
Demo created:
|
|
75
|
+
./memorix-demo
|
|
76
76
|
|
|
77
77
|
Start the server with:
|
|
78
|
-
memorix-explorer --mocks-dir "
|
|
78
|
+
memorix-explorer --mocks-dir "./memorix-demo"
|
|
79
79
|
|
|
80
80
|
Then open:
|
|
81
81
|
http://localhost:5030/demo
|
|
@@ -92,6 +92,21 @@ memorix-explorer create-demo --dir ./fixtures
|
|
|
92
92
|
memorix-explorer --mocks-dir ./fixtures --port 8080
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### Stop the server
|
|
96
|
+
|
|
97
|
+
Press Ctrl+C in the terminal where the server is running, or stop a managed
|
|
98
|
+
instance from another terminal:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
memorix-explorer stop
|
|
102
|
+
memorix-explorer stop --port 8080
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The CLI tracks only server processes it started and refuses to terminate a PID
|
|
106
|
+
that does not identify as Memorix Explorer. Stale tracking files are removed
|
|
107
|
+
automatically. If startup finds an occupied port, the error prints the matching
|
|
108
|
+
stop command and suggests the next port.
|
|
109
|
+
|
|
95
110
|
### With `npx` (no global install)
|
|
96
111
|
|
|
97
112
|
```bash
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[
|
|
2
|
-
{ "recordId": "usr-1", "name": "
|
|
3
|
-
{ "recordId": "usr-2", "name": "
|
|
4
|
-
{ "recordId": "usr-3", "name": "
|
|
2
|
+
{ "recordId": "usr-1", "name": "James", "role": "owner" },
|
|
3
|
+
{ "recordId": "usr-2", "name": "Emily", "role": "engineer" },
|
|
4
|
+
{ "recordId": "usr-3", "name": "Michael", "role": "growth" }
|
|
5
5
|
]
|