@ndevai/ndx2 0.2.3
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 +55 -0
- package/bin/ndx2.js +167 -0
- package/docs/api.md +11 -0
- package/docs/architecture.md +12 -0
- package/docs/constraints.md +10 -0
- package/docs/internals.md +9 -0
- package/docs/overview.md +7 -0
- package/docs/testing.md +14 -0
- package/docs/usage.md +18 -0
- package/package.json +26 -0
- package/templates/docker-compose.yml +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Docker-backed npm launcher for ndx2.
|
|
2
|
+
|
|
3
|
+
| Goal | File |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| Understand purpose | [README.md](README.md) |
|
|
6
|
+
| API reference | [docs/api.md](docs/api.md) |
|
|
7
|
+
| Constraints | [docs/constraints.md](docs/constraints.md) |
|
|
8
|
+
| Testing | [docs/testing.md](docs/testing.md) |
|
|
9
|
+
|
|
10
|
+
# @ndevai/ndx2
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @ndevai/ndx2
|
|
14
|
+
npx ndx2
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
or install globally:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g @ndevai/ndx2
|
|
21
|
+
ndx2
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Without a prior install:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx @ndevai/ndx2
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The CLI does not build the local repository. It writes a dedicated Docker
|
|
31
|
+
Compose file under `~/.ndx2/docker-compose.yml`, records initialization state in
|
|
32
|
+
`~/.ndx2/npm-install.json`, and starts GHCR images for the selected npm
|
|
33
|
+
package version.
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
`ndx2` initializes once, starts the Docker Compose stack, and prints the local
|
|
38
|
+
Agent URL.
|
|
39
|
+
|
|
40
|
+
`ndx2 uninstall` removes the npm initialization flag, the generated compose
|
|
41
|
+
file, and the ndx2 Docker Compose stack. It does not delete the selected ndx
|
|
42
|
+
root volume directory.
|
|
43
|
+
|
|
44
|
+
## Release Contract
|
|
45
|
+
|
|
46
|
+
For each npm package version, publish matching public GHCR tags first:
|
|
47
|
+
|
|
48
|
+
* `ghcr.io/hikamaeng/ndx2-agent:<version>`
|
|
49
|
+
|
|
50
|
+
The npm compose template pulls only this final agent image. `npm/Dockerfile`
|
|
51
|
+
builds it as one distribution image and does not depend on local base-image
|
|
52
|
+
archive files.
|
|
53
|
+
|
|
54
|
+
Only after the GHCR tag is public, multi-architecture, and immutable should
|
|
55
|
+
`@ndevai/ndx2@<version>` be published to npmjs.
|
package/bin/ndx2.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import net from "node:net";
|
|
11
|
+
|
|
12
|
+
const stateDir = join(homedir(), ".ndx2");
|
|
13
|
+
const statePath = join(stateDir, "npm-install.json");
|
|
14
|
+
const composePath = join(stateDir, "docker-compose.yml");
|
|
15
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
+
const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
17
|
+
const version = packageJson.version;
|
|
18
|
+
const templatePath = join(packageRoot, "templates", "docker-compose.yml");
|
|
19
|
+
|
|
20
|
+
const command = process.argv[2] ?? "start";
|
|
21
|
+
|
|
22
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
23
|
+
console.log(version);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
28
|
+
console.log(`ndx2 ${version}
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
ndx2 Initialize if needed, then start Docker containers.
|
|
32
|
+
ndx2 start Same as ndx2.
|
|
33
|
+
ndx2 uninstall Remove the npm initialization flag and ndx2 Docker stack.
|
|
34
|
+
`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (command === "uninstall") {
|
|
39
|
+
uninstall();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (command !== "start") {
|
|
44
|
+
console.error(`Unknown command: ${command}`);
|
|
45
|
+
console.error("Run `ndx2 --help` for usage.");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await start();
|
|
50
|
+
|
|
51
|
+
async function start() {
|
|
52
|
+
ensureDockerReady();
|
|
53
|
+
|
|
54
|
+
if (existsSync(statePath)) {
|
|
55
|
+
const state = JSON.parse(readFileSync(statePath, "utf8"));
|
|
56
|
+
runCompose(["up", "-d"]);
|
|
57
|
+
printReady(state);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const selectedRoot = await askNdxRoot();
|
|
62
|
+
const ndxRoot = resolve(selectedRoot);
|
|
63
|
+
const agentPort = await findPort(18082);
|
|
64
|
+
|
|
65
|
+
mkdirSync(join(ndxRoot, "pgvector"), { recursive: true });
|
|
66
|
+
mkdirSync(stateDir, { recursive: true });
|
|
67
|
+
|
|
68
|
+
const compose = readFileSync(templatePath, "utf8")
|
|
69
|
+
.replaceAll("__IMAGE_TAG__", version)
|
|
70
|
+
.replaceAll("__NDX_ROOT__", dockerPath(ndxRoot))
|
|
71
|
+
.replaceAll("__AGENT_WEB_HOST_PORT__", String(agentPort));
|
|
72
|
+
|
|
73
|
+
writeFileSync(composePath, compose);
|
|
74
|
+
const state = {
|
|
75
|
+
initializedAt: new Date().toISOString(),
|
|
76
|
+
version,
|
|
77
|
+
ndxRoot,
|
|
78
|
+
agentPort,
|
|
79
|
+
composePath
|
|
80
|
+
};
|
|
81
|
+
writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
82
|
+
|
|
83
|
+
runCompose(["up", "-d"]);
|
|
84
|
+
printReady(state);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function askNdxRoot() {
|
|
88
|
+
const rl = createInterface({ input, output });
|
|
89
|
+
try {
|
|
90
|
+
const answer = await rl.question(`ndx root volume path [${process.cwd()}]: `);
|
|
91
|
+
return answer.trim() === "" ? process.cwd() : answer.trim();
|
|
92
|
+
} finally {
|
|
93
|
+
rl.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ensureDockerReady() {
|
|
98
|
+
const docker = spawnSync("docker", ["--version"], { encoding: "utf8" });
|
|
99
|
+
if (docker.status !== 0) {
|
|
100
|
+
console.error("ndx2 runs only in a Docker-capable environment. Install Docker and try again.");
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const info = spawnSync("docker", ["info"], { encoding: "utf8" });
|
|
105
|
+
if (info.status !== 0) {
|
|
106
|
+
console.error("Docker is installed, but the Docker daemon is not available. Start Docker and try again.");
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const compose = spawnSync("docker", ["compose", "version"], { encoding: "utf8" });
|
|
111
|
+
if (compose.status !== 0) {
|
|
112
|
+
console.error("ndx2 requires Docker Compose v2 (`docker compose`). Install or enable it and try again.");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function runCompose(args) {
|
|
118
|
+
execFileSync("docker", ["compose", "-f", composePath, ...args], { stdio: "inherit" });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function uninstall() {
|
|
122
|
+
if (existsSync(composePath)) {
|
|
123
|
+
const docker = spawnSync("docker", ["compose", "version"], { encoding: "utf8" });
|
|
124
|
+
if (docker.status === 0) {
|
|
125
|
+
runCompose(["down", "--remove-orphans"]);
|
|
126
|
+
} else {
|
|
127
|
+
console.error("Docker Compose is not available, so only local ndx2 npm state will be removed.");
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
console.log("No ndx2 npm compose file was found.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
rmSync(statePath, { force: true });
|
|
134
|
+
rmSync(composePath, { force: true });
|
|
135
|
+
console.log("ndx2 npm initialization state and Docker stack have been removed.");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function printReady(state) {
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log("ndx2 is running.");
|
|
141
|
+
console.log(`Agent: http://localhost:${state.agentPort}`);
|
|
142
|
+
console.log(`Root volume: ${state.ndxRoot}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function dockerPath(path) {
|
|
146
|
+
return process.platform === "win32" ? path.replaceAll("\\", "/") : path;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function findPort(startAt) {
|
|
150
|
+
for (let port = startAt; port <= 18999; port += 1) {
|
|
151
|
+
if (await isPortFree(port)) {
|
|
152
|
+
return port;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
throw new Error("No free ndx2 host port found in 18081-18999.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isPortFree(port) {
|
|
159
|
+
return new Promise((resolvePort) => {
|
|
160
|
+
const server = net.createServer();
|
|
161
|
+
server.once("error", () => resolvePort(false));
|
|
162
|
+
server.once("listening", () => {
|
|
163
|
+
server.close(() => resolvePort(true));
|
|
164
|
+
});
|
|
165
|
+
server.listen(port, "127.0.0.1");
|
|
166
|
+
});
|
|
167
|
+
}
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
The package exposes one binary:
|
|
4
|
+
|
|
5
|
+
| Command | Behavior |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `ndx2` | Initialize if needed, then start the saved compose stack. |
|
|
8
|
+
| `ndx2 start` | Same as `ndx2`. |
|
|
9
|
+
| `ndx2 uninstall` | Run compose down, remove npm initialization state, and keep the selected ndx root volume directory. |
|
|
10
|
+
| `ndx2 --help` | Print command help. |
|
|
11
|
+
| `ndx2 --version` | Print the package version. |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
The CLI uses Node built-ins only. It checks Docker and Docker Compose v2, stores
|
|
4
|
+
initialization state in `~/.ndx2/npm-install.json`, and controls
|
|
5
|
+
`~/.ndx2/docker-compose.yml`.
|
|
6
|
+
|
|
7
|
+
The compose stack uses one public GHCR runtime image:
|
|
8
|
+
|
|
9
|
+
* `ghcr.io/hikamaeng/ndx2-agent:<version>`
|
|
10
|
+
|
|
11
|
+
The npm user pulls that single final image. Repository release builds create it
|
|
12
|
+
from `npm/Dockerfile`, not from local base-image archive files.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Constraints
|
|
2
|
+
|
|
3
|
+
The CLI must not build repository code. It only starts already-published public
|
|
4
|
+
GHCR images.
|
|
5
|
+
|
|
6
|
+
The package must not include npm tokens, GitHub tokens, generated compose state,
|
|
7
|
+
or user volume data.
|
|
8
|
+
|
|
9
|
+
The compose template must keep PostgreSQL internal to the compose network and
|
|
10
|
+
must not expose a host PostgreSQL port.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Internals
|
|
2
|
+
|
|
3
|
+
`bin/ndx2.js` resolves the image tag from `npm/package.json`.
|
|
4
|
+
|
|
5
|
+
The generated compose file binds the selected ndx root to `/ndx` and stores
|
|
6
|
+
PostgreSQL data under `<ndx-root>/pgvector`.
|
|
7
|
+
|
|
8
|
+
GitHub Actions publish GHCR images before npm publication. The npm publish
|
|
9
|
+
workflow expects the repository secret `NPM_TOKEN`.
|
package/docs/overview.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
`@ndevai/ndx2` installs the `ndx2` command for end-user Docker launch.
|
|
4
|
+
|
|
5
|
+
It is not the source build workflow. It writes npm-owned state under `~/.ndx2`,
|
|
6
|
+
generates a compose file from `templates/docker-compose.yml`, and starts public
|
|
7
|
+
GHCR images for the package version.
|
package/docs/testing.md
ADDED
package/docs/usage.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Usage
|
|
2
|
+
|
|
3
|
+
Install in a project:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @ndevai/ndx2
|
|
7
|
+
npx ndx2
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Install globally:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install -g @ndevai/ndx2
|
|
14
|
+
ndx2
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The first run asks for the ndx root volume path. Empty input selects the current
|
|
18
|
+
directory.
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ndevai/ndx2",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Docker-backed installer and launcher for ndx2.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ndx2": "bin/ndx2.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"docs",
|
|
13
|
+
"templates",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"check": "node ./bin/ndx2.js --help"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: ndx2
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
agent:
|
|
5
|
+
image: ghcr.io/hikamaeng/ndx2-agent:__IMAGE_TAG__
|
|
6
|
+
container_name: ndx2-agent
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
ports:
|
|
9
|
+
- "__AGENT_WEB_HOST_PORT__:18080"
|
|
10
|
+
extra_hosts:
|
|
11
|
+
- "host.docker.internal:host-gateway"
|
|
12
|
+
volumes:
|
|
13
|
+
- type: bind
|
|
14
|
+
source: "__NDX_ROOT__"
|
|
15
|
+
target: /ndx
|
|
16
|
+
- type: bind
|
|
17
|
+
source: /var/run/docker.sock
|
|
18
|
+
target: /var/run/docker.sock
|
|
19
|
+
environment:
|
|
20
|
+
- NDX_ROOT=/ndx
|