@nitsan-ai/ragsuite-test 0.1.4 → 0.1.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/README.md +34 -25
- package/package.json +2 -3
- package/src/commands/doctor.js +16 -16
- package/src/commands/init.js +48 -45
- package/src/commands/logs.js +23 -35
- package/src/commands/start.js +12 -37
- package/src/commands/stop.js +15 -24
- package/src/commands/update.js +13 -43
- package/src/index.js +6 -7
- package/src/utils/args.js +10 -3
- package/src/utils/config.js +11 -26
- package/src/utils/distribution.js +7 -13
- package/src/utils/git-install.js +1 -1
- package/src/utils/paths.js +35 -7
- package/src/utils/port.js +2 -3
- package/src/utils/prereqs.js +25 -65
- package/src/utils/images-install.js +0 -106
- package/templates/images/.env.example +0 -76
- package/templates/images/docker-compose.yml +0 -165
- package/templates/images/scripts/docker-doctor-images.sh +0 -48
- package/templates/images/scripts/docker-start-images.sh +0 -107
- package/templates/images/scripts/docker-stop-images.sh +0 -22
package/src/commands/update.js
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { resolveRepoRoot,
|
|
5
|
+
const { resolveRepoRoot, assertNativeDeploy, looksLikeGitCheckout } = require('../utils/paths');
|
|
6
6
|
const { readConfig } = require('../utils/config');
|
|
7
7
|
const { setActiveInstall } = require('../utils/global-config');
|
|
8
|
-
const { captureCommand
|
|
8
|
+
const { captureCommand } = require('../utils/spawn');
|
|
9
9
|
const { info, warn, error } = require('../utils/log');
|
|
10
10
|
|
|
11
11
|
const name = 'update';
|
|
12
|
-
const summary = '
|
|
12
|
+
const summary = 'git pull in place — keeps .env and data (no Docker)';
|
|
13
13
|
|
|
14
14
|
function help() {
|
|
15
15
|
return `Usage: ragsuite-test update
|
|
16
16
|
|
|
17
|
-
Upgrades the active install without wiping data:
|
|
18
|
-
•
|
|
19
|
-
•
|
|
17
|
+
Upgrades the active git install without wiping data:
|
|
18
|
+
• git pull --ff-only in the install dir
|
|
19
|
+
• Keeps .env and any host Postgres/Redis/Chroma data
|
|
20
20
|
|
|
21
|
-
Never runs docker compose
|
|
21
|
+
Never runs docker compose (no volume risk).
|
|
22
22
|
|
|
23
23
|
Typical flow after a new release:
|
|
24
24
|
npm install -g @nitsan-ai/ragsuite-test@latest
|
|
@@ -39,6 +39,7 @@ function run(ctx) {
|
|
|
39
39
|
repoRootFlag: ctx.globals.repoRoot,
|
|
40
40
|
env: ctx.env,
|
|
41
41
|
});
|
|
42
|
+
assertNativeDeploy(repoRoot);
|
|
42
43
|
} catch (err) {
|
|
43
44
|
error(err.message);
|
|
44
45
|
return 1;
|
|
@@ -46,48 +47,18 @@ function run(ctx) {
|
|
|
46
47
|
|
|
47
48
|
setActiveInstall(repoRoot);
|
|
48
49
|
const cfg = readConfig(repoRoot);
|
|
49
|
-
const source = (cfg && cfg.source) ||
|
|
50
|
-
const images = source === 'images' || isImagesInstall(repoRoot);
|
|
50
|
+
const source = (cfg && cfg.source) || 'git';
|
|
51
51
|
|
|
52
52
|
info(`Updating install: ${repoRoot}`);
|
|
53
|
-
info(` source=${source}`);
|
|
54
|
-
info(' (.env and
|
|
53
|
+
info(` source=${source} mode=native`);
|
|
54
|
+
info(' (.env and database data are kept)');
|
|
55
55
|
info('');
|
|
56
56
|
|
|
57
57
|
if (ctx.globals.dryRun) {
|
|
58
|
-
info(
|
|
58
|
+
info('[dry-run] would update via git pull --ff-only');
|
|
59
59
|
return 0;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (images) {
|
|
63
|
-
info('Pulling newer Docker images…');
|
|
64
|
-
const pull = captureCommand(
|
|
65
|
-
'docker',
|
|
66
|
-
['compose', '-f', 'docker-compose.yml', 'pull'],
|
|
67
|
-
{ cwd: repoRoot, env: ctx.env },
|
|
68
|
-
);
|
|
69
|
-
if (pull.status !== 0) {
|
|
70
|
-
error(`Image pull failed (exit ${pull.status}).`);
|
|
71
|
-
if (pull.stderr) error(String(pull.stderr).trimEnd());
|
|
72
|
-
error('Publish GHCR images / check IMAGE_TAG in .env, then retry.');
|
|
73
|
-
return 1;
|
|
74
|
-
}
|
|
75
|
-
info('Images pulled.');
|
|
76
|
-
info('');
|
|
77
|
-
info('Apply update:');
|
|
78
|
-
info(' ragsuite-test stop');
|
|
79
|
-
info(' ragsuite-test start');
|
|
80
|
-
info('(or: ragsuite-test start — compose recreate with new images)');
|
|
81
|
-
// Recreate containers with new images without wiping volumes
|
|
82
|
-
info('');
|
|
83
|
-
info('Recreating containers with new images (volumes preserved)…');
|
|
84
|
-
return runScript(repoRoot, path.join('scripts', 'docker-start-images.sh'), ['--detach'], {
|
|
85
|
-
dryRun: false,
|
|
86
|
-
env: { ...ctx.env, RAGSUITE_MODE: 'docker' },
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Git / checkout install
|
|
91
62
|
if (!looksLikeGitCheckout(repoRoot) && !fs.existsSync(path.join(repoRoot, '.git'))) {
|
|
92
63
|
error('This install has no .git directory. Re-run: ragsuite-test init --force');
|
|
93
64
|
return 1;
|
|
@@ -113,8 +84,7 @@ function run(ctx) {
|
|
|
113
84
|
info('Restart to apply:');
|
|
114
85
|
info(' ragsuite-test stop');
|
|
115
86
|
info(' ragsuite-test start');
|
|
116
|
-
info('');
|
|
117
|
-
info('Never: docker compose down -v (would delete Postgres/Chroma data)');
|
|
87
|
+
info(' # or: cd install && npm run stop && npm start');
|
|
118
88
|
return 0;
|
|
119
89
|
}
|
|
120
90
|
|
package/src/index.js
CHANGED
|
@@ -19,7 +19,8 @@ function globalHelp() {
|
|
|
19
19
|
'RAGSuite test CLI (experimental)',
|
|
20
20
|
'',
|
|
21
21
|
'Usage: ragsuite-test <command> [options]',
|
|
22
|
-
'
|
|
22
|
+
'',
|
|
23
|
+
'Deploy path: git clone + native npm scripts (no Docker).',
|
|
23
24
|
'',
|
|
24
25
|
'Commands:',
|
|
25
26
|
];
|
|
@@ -30,9 +31,8 @@ function globalHelp() {
|
|
|
30
31
|
'',
|
|
31
32
|
'Global options:',
|
|
32
33
|
' --help, -h Show help',
|
|
33
|
-
' --repo-root <path>
|
|
34
|
+
' --repo-root <path> Override active install',
|
|
34
35
|
' --from-git [url] init: clone source (default public repo)',
|
|
35
|
-
' --from-images init: Docker images only (no app source on disk)',
|
|
36
36
|
' --install-dir <path> init: install target (default ~/ragsuite-test)',
|
|
37
37
|
' --llm-api-key <key> init: optional override for CUSTOM_LLM_INTERNAL_API_KEY',
|
|
38
38
|
' --smtp-host <host> init: optional SMTP override',
|
|
@@ -40,11 +40,10 @@ function globalHelp() {
|
|
|
40
40
|
' --smtp-password <pass> init: optional SMTP override',
|
|
41
41
|
' --email-from <email> init: optional SMTP override',
|
|
42
42
|
' --force init: overwrite non-empty install dir / .env',
|
|
43
|
-
' --yes, -y Non-interactive
|
|
44
|
-
' --mode <docker|native> Override config (Docker optional)',
|
|
43
|
+
' --yes, -y Non-interactive',
|
|
45
44
|
' --dry-run Print actions without running (start/stop/doctor/logs/update)',
|
|
46
45
|
'',
|
|
47
|
-
'Active install: ~/.ragsuite-test/config.json (set by init
|
|
46
|
+
'Active install: ~/.ragsuite-test/config.json (set by init)',
|
|
48
47
|
'Env: RAGSUITE_REPO_ROOT or RAGSUITE_INSTALL_DIR',
|
|
49
48
|
' RAGSUITE_TEST_LLM_API_KEY, RAGSUITE_TEST_SMTP_* , RAGSUITE_TEST_EMAIL_FROM',
|
|
50
49
|
'',
|
|
@@ -94,7 +93,7 @@ async function main(argv = process.argv) {
|
|
|
94
93
|
const code = typeof result?.then === 'function' ? await result : result;
|
|
95
94
|
return typeof code === 'number' ? code : 0;
|
|
96
95
|
} catch (err) {
|
|
97
|
-
if (err && (err.code === 'NOT_REPO_ROOT' || err.code === 'USAGE' || err.code === 'INSTALL_DIR_NONEMPTY' || err.code === 'ENV_EXISTS' || err.code === 'ENV_INCOMPLETE' || err.code === 'PORT_IN_USE' || err.code === 'LLM_KEY_INVALID' || err.code === 'SMTP_REQUIRED' || err.code === 'GIT_CLONE_FAILED' || err.code === 'MISSING_GIT')) {
|
|
96
|
+
if (err && (err.code === 'NOT_REPO_ROOT' || err.code === 'USAGE' || err.code === 'INSTALL_DIR_NONEMPTY' || err.code === 'ENV_EXISTS' || err.code === 'ENV_INCOMPLETE' || err.code === 'PORT_IN_USE' || err.code === 'LLM_KEY_INVALID' || err.code === 'SMTP_REQUIRED' || err.code === 'GIT_CLONE_FAILED' || err.code === 'MISSING_GIT' || err.code === 'IMAGES_REMOVED' || err.code === 'NATIVE_SCRIPT_MISSING')) {
|
|
98
97
|
error(err.message);
|
|
99
98
|
return 1;
|
|
100
99
|
}
|
package/src/utils/args.js
CHANGED
|
@@ -72,7 +72,7 @@ function parseArgv(argv) {
|
|
|
72
72
|
const { value, nextI } = takeValue('--mode', t, i);
|
|
73
73
|
i = nextI;
|
|
74
74
|
if (!value || String(value).startsWith('-')) {
|
|
75
|
-
const err = new Error('--mode requires docker
|
|
75
|
+
const err = new Error('--mode requires native (docker was removed from deployment)');
|
|
76
76
|
err.code = 'USAGE';
|
|
77
77
|
throw err;
|
|
78
78
|
}
|
|
@@ -208,8 +208,15 @@ function parseArgv(argv) {
|
|
|
208
208
|
|
|
209
209
|
function normalizeMode(value) {
|
|
210
210
|
const m = String(value).trim().toLowerCase();
|
|
211
|
-
if (m
|
|
212
|
-
const err = new Error(
|
|
211
|
+
if (m === 'docker') {
|
|
212
|
+
const err = new Error(
|
|
213
|
+
'Docker mode was removed from deployment. Use native (omit --mode or pass --mode native).',
|
|
214
|
+
);
|
|
215
|
+
err.code = 'USAGE';
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
if (m !== 'native') {
|
|
219
|
+
const err = new Error(`Invalid mode "${value}" (use native)`);
|
|
213
220
|
err.code = 'USAGE';
|
|
214
221
|
throw err;
|
|
215
222
|
}
|
package/src/utils/config.js
CHANGED
|
@@ -10,11 +10,11 @@ function configPath(repoRoot) {
|
|
|
10
10
|
return path.join(repoRoot, CONFIG_DIR, 'config.json');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function defaultConfig(repoRoot
|
|
13
|
+
function defaultConfig(repoRoot) {
|
|
14
14
|
const root = path.resolve(repoRoot);
|
|
15
15
|
return {
|
|
16
16
|
version: CONFIG_VERSION,
|
|
17
|
-
mode:
|
|
17
|
+
mode: 'native',
|
|
18
18
|
source: 'checkout',
|
|
19
19
|
repoRoot: root,
|
|
20
20
|
installDir: root,
|
|
@@ -23,7 +23,9 @@ function defaultConfig(repoRoot, mode = 'docker') {
|
|
|
23
23
|
|
|
24
24
|
function normalizeSource(raw) {
|
|
25
25
|
const s = String(raw || '').trim();
|
|
26
|
-
if (s === '
|
|
26
|
+
if (s === 'git' || s === 'checkout' || s === 'zip') return s === 'zip' ? 'checkout' : s;
|
|
27
|
+
// Legacy images installs are no longer supported for start/stop
|
|
28
|
+
if (s === 'images') return 'images';
|
|
27
29
|
return 'checkout';
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -39,7 +41,7 @@ function readConfig(repoRoot) {
|
|
|
39
41
|
}
|
|
40
42
|
return {
|
|
41
43
|
version: Number(raw.version) || CONFIG_VERSION,
|
|
42
|
-
mode:
|
|
44
|
+
mode: 'native',
|
|
43
45
|
source: normalizeSource(raw.source),
|
|
44
46
|
repoRoot: path.resolve(String(raw.repoRoot || repoRoot)),
|
|
45
47
|
installDir: path.resolve(String(raw.installDir || raw.repoRoot || repoRoot)),
|
|
@@ -62,37 +64,20 @@ function writeConfig(repoRoot, partial = {}) {
|
|
|
62
64
|
...existing,
|
|
63
65
|
...partial,
|
|
64
66
|
version: CONFIG_VERSION,
|
|
65
|
-
source,
|
|
67
|
+
source: source === 'images' ? 'git' : source,
|
|
66
68
|
repoRoot: path.resolve(partial.repoRoot || existing.repoRoot || repoRoot),
|
|
67
69
|
installDir: path.resolve(
|
|
68
70
|
partial.installDir || existing.installDir || partial.repoRoot || existing.repoRoot || repoRoot,
|
|
69
71
|
),
|
|
70
|
-
mode:
|
|
72
|
+
mode: 'native',
|
|
71
73
|
};
|
|
72
74
|
fs.writeFileSync(configPath(repoRoot), `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
73
75
|
return next;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
function resolveMode({ flagMode, repoRoot, env = process.env } = {}) {
|
|
80
|
-
if (flagMode === 'docker' || flagMode === 'native') {
|
|
81
|
-
return flagMode;
|
|
82
|
-
}
|
|
83
|
-
if (repoRoot) {
|
|
84
|
-
const cfg = readConfig(repoRoot);
|
|
85
|
-
if (cfg && (cfg.mode === 'docker' || cfg.mode === 'native')) {
|
|
86
|
-
return cfg.mode;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
const fromEnv = String(env.RAGSUITE_MODE || '')
|
|
90
|
-
.trim()
|
|
91
|
-
.toLowerCase();
|
|
92
|
-
if (fromEnv === 'native') {
|
|
93
|
-
return 'native';
|
|
94
|
-
}
|
|
95
|
-
return 'docker';
|
|
78
|
+
/** Deployment always uses native npm scripts. */
|
|
79
|
+
function resolveMode() {
|
|
80
|
+
return 'native';
|
|
96
81
|
}
|
|
97
82
|
|
|
98
83
|
module.exports = {
|
|
@@ -1,42 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Distribution
|
|
5
|
-
*
|
|
6
|
-
* - git-clone: `ragsuite-test init` (source on disk)
|
|
7
|
-
* - images: `ragsuite-test init --from-images` (pull GHCR, no app source)
|
|
8
|
-
* - local-checkout: --repo-root
|
|
4
|
+
* Distribution for the testing CLI.
|
|
5
|
+
* Deploy path is git clone + native npm scripts only (no Docker).
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
8
|
const MODE_LOCAL = 'local-checkout';
|
|
12
9
|
const MODE_GIT = 'git-clone';
|
|
13
|
-
const MODE_IMAGES = 'images';
|
|
14
|
-
const MODE_REGISTRY = 'registry-compose';
|
|
15
10
|
|
|
16
11
|
function assertSupportedInstall() {}
|
|
17
12
|
|
|
18
13
|
function resolveComposeBundle() {
|
|
19
|
-
const err = new Error(
|
|
20
|
-
'Use: ragsuite-test init --from-images (pull-only) OR ragsuite-test init (git source)',
|
|
21
|
-
);
|
|
14
|
+
const err = new Error('Docker compose bundles were removed from deployment. Use: ragsuite-test init');
|
|
22
15
|
err.code = 'DIST_NOT_READY';
|
|
23
16
|
throw err;
|
|
24
17
|
}
|
|
25
18
|
|
|
26
19
|
function installHint() {
|
|
27
20
|
return [
|
|
28
|
-
'Day-to-day:',
|
|
21
|
+
'Day-to-day (native / npm scripts — no Docker):',
|
|
29
22
|
' ragsuite-test start',
|
|
30
23
|
' ragsuite-test update',
|
|
31
24
|
' ragsuite-test stop',
|
|
25
|
+
'Or in the install dir:',
|
|
26
|
+
' npm start',
|
|
27
|
+
' npm run stop',
|
|
32
28
|
].join('\n');
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
module.exports = {
|
|
36
32
|
MODE_LOCAL,
|
|
37
33
|
MODE_GIT,
|
|
38
|
-
MODE_IMAGES,
|
|
39
|
-
MODE_REGISTRY,
|
|
40
34
|
mode: MODE_GIT,
|
|
41
35
|
assertSupportedInstall,
|
|
42
36
|
resolveComposeBundle,
|
package/src/utils/git-install.js
CHANGED
|
@@ -81,7 +81,7 @@ function cloneGitToInstallDir(gitUrl, options = {}) {
|
|
|
81
81
|
const repoRoot = target;
|
|
82
82
|
if (!looksLikeRepoRoot(repoRoot) && !looksLikeFullBundleRoot(repoRoot)) {
|
|
83
83
|
const err = new Error(
|
|
84
|
-
`Cloned repo at ${repoRoot} does not look like a RAGSuite monorepo (missing scripts/docker-start.sh).`,
|
|
84
|
+
`Cloned repo at ${repoRoot} does not look like a RAGSuite monorepo (missing scripts/native-start.sh or docker-start.sh).`,
|
|
85
85
|
);
|
|
86
86
|
err.code = 'GIT_INVALID_REPO';
|
|
87
87
|
throw err;
|
package/src/utils/paths.js
CHANGED
|
@@ -12,9 +12,11 @@ function defaultInstallDir() {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function looksLikeGitCheckout(dir) {
|
|
15
|
-
const start = path.join(dir, 'scripts', 'docker-start.sh');
|
|
16
15
|
const pkg = path.join(dir, 'package.json');
|
|
17
|
-
|
|
16
|
+
const native = path.join(dir, 'scripts', 'native-start.sh');
|
|
17
|
+
const docker = path.join(dir, 'scripts', 'docker-start.sh');
|
|
18
|
+
// Prefer native marker; still recognize older checkouts that only had docker-start.sh
|
|
19
|
+
return fs.existsSync(pkg) && (fs.existsSync(native) || fs.existsSync(docker));
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function looksLikeImagesInstall(dir) {
|
|
@@ -30,16 +32,15 @@ function looksLikeImagesInstall(dir) {
|
|
|
30
32
|
/* fall through */
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
|
-
return !fs.existsSync(path.join(dir, 'scripts', '
|
|
35
|
+
return !fs.existsSync(path.join(dir, 'scripts', 'native-start.sh'));
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function looksLikeRepoRoot(dir) {
|
|
37
|
-
return looksLikeGitCheckout(dir)
|
|
39
|
+
return looksLikeGitCheckout(dir);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
function looksLikeFullBundleRoot(dir) {
|
|
41
|
-
|
|
42
|
-
return fs.existsSync(path.join(dir, 'docker-compose.yml'));
|
|
43
|
+
return looksLikeGitCheckout(dir);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function walkUpForRepoRoot(startDir) {
|
|
@@ -130,7 +131,6 @@ function resolveRepoRoot({
|
|
|
130
131
|
[
|
|
131
132
|
'No RAGSuite install found.',
|
|
132
133
|
' First time: ragsuite-test init',
|
|
133
|
-
' Or images: ragsuite-test init --from-images',
|
|
134
134
|
' Then: ragsuite-test start',
|
|
135
135
|
].join('\n'),
|
|
136
136
|
);
|
|
@@ -158,6 +158,33 @@ function isImagesInstall(repoRoot) {
|
|
|
158
158
|
return looksLikeImagesInstall(repoRoot) && !looksLikeGitCheckout(repoRoot);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Reject legacy images-only installs (Docker deployment removed).
|
|
163
|
+
*/
|
|
164
|
+
function assertNativeDeploy(repoRoot) {
|
|
165
|
+
if (isImagesInstall(repoRoot)) {
|
|
166
|
+
const err = new Error(
|
|
167
|
+
[
|
|
168
|
+
'This install was created with --from-images (Docker). That path was removed.',
|
|
169
|
+
' Your Docker volumes were NOT touched.',
|
|
170
|
+
' Reinstall with source + native npm scripts:',
|
|
171
|
+
' ragsuite-test init --install-dir ~/ragsuite-test-native',
|
|
172
|
+
' ragsuite-test start',
|
|
173
|
+
].join('\n'),
|
|
174
|
+
);
|
|
175
|
+
err.code = 'IMAGES_REMOVED';
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
const native = path.join(repoRoot, 'scripts', 'native-start.sh');
|
|
179
|
+
if (!fs.existsSync(native)) {
|
|
180
|
+
const err = new Error(
|
|
181
|
+
`Missing scripts/native-start.sh in ${repoRoot}. Run: ragsuite-test update (or re-init)`,
|
|
182
|
+
);
|
|
183
|
+
err.code = 'NATIVE_SCRIPT_MISSING';
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
161
188
|
function isDirEmptyOrMissing(dir) {
|
|
162
189
|
if (!fs.existsSync(dir)) return true;
|
|
163
190
|
return fs.readdirSync(dir).filter((n) => n !== '.DS_Store').length === 0;
|
|
@@ -176,5 +203,6 @@ module.exports = {
|
|
|
176
203
|
ensureRagsuiteDir,
|
|
177
204
|
nativeLogDir,
|
|
178
205
|
isImagesInstall,
|
|
206
|
+
assertNativeDeploy,
|
|
179
207
|
isDirEmptyOrMissing,
|
|
180
208
|
};
|
package/src/utils/port.js
CHANGED
|
@@ -67,14 +67,13 @@ async function assertPortsFree(ports = DEFAULT_INSTALL_PORTS) {
|
|
|
67
67
|
'',
|
|
68
68
|
'If a previous RAGSuite install is running:',
|
|
69
69
|
' ragsuite-test stop',
|
|
70
|
-
' # or
|
|
70
|
+
' # or: npm run stop',
|
|
71
71
|
'',
|
|
72
72
|
'See what owns the ports (macOS/Linux):',
|
|
73
73
|
' lsof -nP -iTCP:9090 -sTCP:LISTEN',
|
|
74
|
-
' lsof -nP -iTCP:
|
|
74
|
+
' lsof -nP -iTCP:9191 -sTCP:LISTEN',
|
|
75
75
|
' lsof -nP -iTCP:5436 -sTCP:LISTEN',
|
|
76
76
|
' lsof -nP -iTCP:6382 -sTCP:LISTEN',
|
|
77
|
-
' docker ps',
|
|
78
77
|
].join('\n'),
|
|
79
78
|
);
|
|
80
79
|
err.code = 'PORT_IN_USE';
|
package/src/utils/prereqs.js
CHANGED
|
@@ -1,59 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
-
const { info,
|
|
4
|
+
const { info, error } = require('./log');
|
|
5
5
|
|
|
6
6
|
function commandExists(cmd) {
|
|
7
7
|
const r = spawnSync('which', [cmd], { encoding: 'utf8' });
|
|
8
8
|
return r.status === 0;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function dockerDaemonRunning() {
|
|
12
|
-
const r = spawnSync('docker', ['info'], {
|
|
13
|
-
encoding: 'utf8',
|
|
14
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
|
-
});
|
|
16
|
-
return r.status === 0;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
11
|
function nodeMajorOk() {
|
|
20
12
|
const major = Number(String(process.versions.node).split('.')[0]);
|
|
21
13
|
return Number.isFinite(major) && major >= 18;
|
|
22
14
|
}
|
|
23
15
|
|
|
24
|
-
/**
|
|
25
|
-
* How to install a missing prerequisite (shown in doctor/init).
|
|
26
|
-
*/
|
|
27
16
|
function installHintFor(issueKey) {
|
|
28
17
|
const hints = {
|
|
29
18
|
node: 'Install Node 18+: https://nodejs.org/ or `brew install node` / `sudo apt install nodejs`',
|
|
30
|
-
docker:
|
|
31
|
-
'Docker is optional. Install Docker Desktop, OR use --mode native (Postgres :5436 + Redis :6382 on the host).',
|
|
32
|
-
'docker-compose': 'Install Docker Desktop (includes compose) or the docker compose plugin.',
|
|
33
19
|
python3: 'Install Python 3: `brew install python` / `sudo apt install python3 python3-venv`',
|
|
34
|
-
yarn: '
|
|
20
|
+
yarn: '`corepack enable && corepack prepare yarn@stable --activate` or `npm i -g yarn`',
|
|
35
21
|
git: 'Install git: macOS `xcode-select --install` / `brew install git`; Linux `sudo apt install git`',
|
|
36
22
|
postgres:
|
|
37
|
-
'
|
|
38
|
-
redis: '
|
|
23
|
+
'Postgres on localhost:5436, db ragsuite_v3. Example: `brew install postgresql@15` then create DB/user.',
|
|
24
|
+
redis: 'Redis on localhost:6382. Example: `brew install redis` then `redis-server --port 6382`.',
|
|
39
25
|
};
|
|
40
26
|
return hints[issueKey] || null;
|
|
41
27
|
}
|
|
42
28
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
* Docker is never required — prefer native if Docker is unavailable.
|
|
46
|
-
*/
|
|
47
|
-
function resolvePreferredMode(flagMode) {
|
|
48
|
-
if (flagMode === 'docker' || flagMode === 'native') return flagMode;
|
|
49
|
-
if (commandExists('docker') && dockerDaemonRunning()) return 'docker';
|
|
29
|
+
/** Deployment is always native (npm scripts). */
|
|
30
|
+
function resolvePreferredMode() {
|
|
50
31
|
return 'native';
|
|
51
32
|
}
|
|
52
33
|
|
|
53
34
|
/**
|
|
54
|
-
* Check prerequisites for
|
|
35
|
+
* Check prerequisites for native deploy. Returns { ok, issues[], notes[], hints[] }.
|
|
55
36
|
*/
|
|
56
|
-
function checkPrereqs(
|
|
37
|
+
function checkPrereqs(_mode = 'native', options = {}) {
|
|
57
38
|
const { needGit = false } = options;
|
|
58
39
|
const issues = [];
|
|
59
40
|
const notes = [];
|
|
@@ -73,44 +54,28 @@ function checkPrereqs(mode = 'native', options = {}) {
|
|
|
73
54
|
|
|
74
55
|
if (needGit) {
|
|
75
56
|
if (!commandExists('git')) {
|
|
76
|
-
pushIssue('git not found (required for
|
|
57
|
+
pushIssue('git not found (required for init)', 'git');
|
|
77
58
|
} else {
|
|
78
59
|
notes.push('git present');
|
|
79
60
|
}
|
|
80
61
|
}
|
|
81
62
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
} else if (!dockerDaemonRunning()) {
|
|
86
|
-
pushIssue('Docker daemon not running — start Docker Desktop / dockerd', 'docker');
|
|
87
|
-
} else {
|
|
88
|
-
notes.push('Docker CLI + daemon OK');
|
|
89
|
-
const compose = spawnSync('docker', ['compose', 'version'], { encoding: 'utf8' });
|
|
90
|
-
if (compose.status !== 0) {
|
|
91
|
-
pushIssue('docker compose plugin not available', 'docker-compose');
|
|
92
|
-
} else {
|
|
93
|
-
notes.push('docker compose OK');
|
|
94
|
-
}
|
|
95
|
-
}
|
|
63
|
+
notes.push('Deploy mode=native (Docker is not used)');
|
|
64
|
+
if (!commandExists('python3')) {
|
|
65
|
+
pushIssue('python3 not found', 'python3');
|
|
96
66
|
} else {
|
|
97
|
-
notes.push('
|
|
98
|
-
if (!commandExists('python3')) {
|
|
99
|
-
pushIssue('python3 not found (required for native mode)', 'python3');
|
|
100
|
-
} else {
|
|
101
|
-
notes.push('python3 present');
|
|
102
|
-
}
|
|
103
|
-
if (!commandExists('yarn')) {
|
|
104
|
-
notes.push('yarn missing — enable with corepack or npm i -g yarn');
|
|
105
|
-
const h = installHintFor('yarn');
|
|
106
|
-
if (h) hints.push(h);
|
|
107
|
-
} else {
|
|
108
|
-
notes.push('yarn present');
|
|
109
|
-
}
|
|
110
|
-
notes.push('Need Postgres :5436 (ragsuite_v3) and Redis :6382 on this machine');
|
|
111
|
-
hints.push(installHintFor('postgres'));
|
|
112
|
-
hints.push(installHintFor('redis'));
|
|
67
|
+
notes.push('python3 present');
|
|
113
68
|
}
|
|
69
|
+
if (!commandExists('yarn')) {
|
|
70
|
+
notes.push('yarn missing — enable with corepack or npm i -g yarn');
|
|
71
|
+
const h = installHintFor('yarn');
|
|
72
|
+
if (h) hints.push(h);
|
|
73
|
+
} else {
|
|
74
|
+
notes.push('yarn present');
|
|
75
|
+
}
|
|
76
|
+
notes.push('Need Postgres :5436 (ragsuite_v3) and Redis :6382 on this machine');
|
|
77
|
+
hints.push(installHintFor('postgres'));
|
|
78
|
+
hints.push(installHintFor('redis'));
|
|
114
79
|
|
|
115
80
|
if (process.platform === 'win32') {
|
|
116
81
|
notes.push('Windows: use WSL or Git Bash — start/stop spawn bash scripts');
|
|
@@ -124,9 +89,9 @@ function checkPrereqs(mode = 'native', options = {}) {
|
|
|
124
89
|
};
|
|
125
90
|
}
|
|
126
91
|
|
|
127
|
-
function printPrereqs(mode, options = {}) {
|
|
92
|
+
function printPrereqs(mode = 'native', options = {}) {
|
|
128
93
|
const { ok, issues, notes, hints } = checkPrereqs(mode, options);
|
|
129
|
-
info(
|
|
94
|
+
info('Prerequisites (native / npm scripts):');
|
|
130
95
|
for (const n of notes) info(` ✓ ${n}`);
|
|
131
96
|
for (const i of issues) error(` ✗ ${i}`);
|
|
132
97
|
if (hints.length) {
|
|
@@ -134,16 +99,11 @@ function printPrereqs(mode, options = {}) {
|
|
|
134
99
|
info('How to get missing pieces:');
|
|
135
100
|
for (const h of hints) info(` → ${h}`);
|
|
136
101
|
}
|
|
137
|
-
if (!ok && mode === 'docker') {
|
|
138
|
-
warn('');
|
|
139
|
-
warn('Docker is optional. Re-run with --mode native if you prefer host Postgres/Redis (no Docker).');
|
|
140
|
-
}
|
|
141
102
|
return ok;
|
|
142
103
|
}
|
|
143
104
|
|
|
144
105
|
module.exports = {
|
|
145
106
|
commandExists,
|
|
146
|
-
dockerDaemonRunning,
|
|
147
107
|
nodeMajorOk,
|
|
148
108
|
installHintFor,
|
|
149
109
|
resolvePreferredMode,
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { defaultInstallDir } = require('./git-install');
|
|
6
|
-
|
|
7
|
-
function templatesRoot() {
|
|
8
|
-
return path.join(__dirname, '..', '..', 'templates', 'images');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function isDirEmptyOrMissing(dir) {
|
|
12
|
-
if (!fs.existsSync(dir)) return true;
|
|
13
|
-
const items = fs.readdirSync(dir).filter((n) => n !== '.DS_Store');
|
|
14
|
-
return items.length === 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function copyFile(src, dest) {
|
|
18
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
19
|
-
fs.copyFileSync(src, dest);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Materialize a pull-only install dir (compose + scripts + .env.example).
|
|
24
|
-
* No monorepo source.
|
|
25
|
-
*/
|
|
26
|
-
function materializeImagesInstall(options = {}) {
|
|
27
|
-
const { installDir = defaultInstallDir(), force = false } = options;
|
|
28
|
-
const srcRoot = templatesRoot();
|
|
29
|
-
if (!fs.existsSync(path.join(srcRoot, 'docker-compose.yml'))) {
|
|
30
|
-
const err = new Error(
|
|
31
|
-
`Images templates missing in CLI package (${srcRoot}). Reinstall @nitsan-ai/ragsuite-test.`,
|
|
32
|
-
);
|
|
33
|
-
err.code = 'IMAGES_TEMPLATE_MISSING';
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const target = path.resolve(installDir);
|
|
38
|
-
if (!isDirEmptyOrMissing(target)) {
|
|
39
|
-
if (!force) {
|
|
40
|
-
const err = new Error(
|
|
41
|
-
[
|
|
42
|
-
`Install directory is not empty: ${target}`,
|
|
43
|
-
' If already installed: ragsuite-test start',
|
|
44
|
-
' To upgrade in place: ragsuite-test update',
|
|
45
|
-
' To wipe & reinstall: ragsuite-test init --from-images --force',
|
|
46
|
-
' Or use another path: ragsuite-test init --from-images --install-dir <path>',
|
|
47
|
-
].join('\n'),
|
|
48
|
-
);
|
|
49
|
-
err.code = 'INSTALL_DIR_NONEMPTY';
|
|
50
|
-
throw err;
|
|
51
|
-
}
|
|
52
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
fs.mkdirSync(target, { recursive: true });
|
|
56
|
-
copyFile(
|
|
57
|
-
path.join(srcRoot, 'docker-compose.yml'),
|
|
58
|
-
path.join(target, 'docker-compose.yml'),
|
|
59
|
-
);
|
|
60
|
-
copyFile(
|
|
61
|
-
path.join(srcRoot, '.env.example'),
|
|
62
|
-
path.join(target, '.env.example'),
|
|
63
|
-
);
|
|
64
|
-
copyFile(
|
|
65
|
-
path.join(srcRoot, 'scripts', 'docker-start-images.sh'),
|
|
66
|
-
path.join(target, 'scripts', 'docker-start-images.sh'),
|
|
67
|
-
);
|
|
68
|
-
copyFile(
|
|
69
|
-
path.join(srcRoot, 'scripts', 'docker-stop-images.sh'),
|
|
70
|
-
path.join(target, 'scripts', 'docker-stop-images.sh'),
|
|
71
|
-
);
|
|
72
|
-
copyFile(
|
|
73
|
-
path.join(srcRoot, 'scripts', 'docker-doctor-images.sh'),
|
|
74
|
-
path.join(target, 'scripts', 'docker-doctor-images.sh'),
|
|
75
|
-
);
|
|
76
|
-
try {
|
|
77
|
-
fs.chmodSync(path.join(target, 'scripts', 'docker-start-images.sh'), 0o755);
|
|
78
|
-
fs.chmodSync(path.join(target, 'scripts', 'docker-stop-images.sh'), 0o755);
|
|
79
|
-
fs.chmodSync(path.join(target, 'scripts', 'docker-doctor-images.sh'), 0o755);
|
|
80
|
-
} catch {
|
|
81
|
-
/* windows */
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Minimal package.json so tooling can identify the install dir
|
|
85
|
-
fs.writeFileSync(
|
|
86
|
-
path.join(target, 'package.json'),
|
|
87
|
-
`${JSON.stringify(
|
|
88
|
-
{
|
|
89
|
-
name: 'ragsuite-images-install',
|
|
90
|
-
private: true,
|
|
91
|
-
description: 'RAGSuite pull-only images install (no source tree)',
|
|
92
|
-
},
|
|
93
|
-
null,
|
|
94
|
-
2,
|
|
95
|
-
)}\n`,
|
|
96
|
-
'utf8',
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
return { repoRoot: target };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
module.exports = {
|
|
103
|
-
templatesRoot,
|
|
104
|
-
materializeImagesInstall,
|
|
105
|
-
defaultInstallDir,
|
|
106
|
-
};
|