@nitsan-ai/ragsuite-test 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nitsan AI / RAGSuite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # RAGSuite test CLI (`ragsuite-test`)
2
+
3
+ > **TESTING PACKAGE** — `@nitsan-ai/ragsuite-test` / `ragsuite-test` is temporary.
4
+ > **Not** the final production `@nitsan-ai/ragsuite` / `ragsuite` CLI (later release).
5
+ > Do **not** treat this as the long-term npm identity.
6
+
7
+ Wraps monorepo Docker/native scripts. Does **not** reimplement compose.
8
+
9
+ Requires **Node.js 18+**.
10
+
11
+ **Run modes (both fully supported):**
12
+
13
+ | Situation | Mode | Command |
14
+ |-----------|------|---------|
15
+ | You have Docker | `docker` (default) | `ragsuite-test init … --mode docker` then `start` |
16
+ | No Docker | `native` | Need Postgres **:5436** + Redis **:6382** on the host, then `ragsuite-test init … --mode native` then `start` |
17
+
18
+ Docker is **not** mandatory — choose native in the wizard or pass `--mode native`.
19
+
20
+ **Full tester steps:** [TEST_DISTRIBUTION.md](./TEST_DISTRIBUTION.md)
21
+
22
+ ## Quick start (wizard)
23
+
24
+ ```bash
25
+ bash scripts/build-release-zip.sh
26
+ cd cli && npm pack && npm install -g ./nitsan-ai-ragsuite-test-*.tgz
27
+
28
+ # Interactive:
29
+ ragsuite-test init
30
+
31
+ # Or CI / noninteractive:
32
+ ragsuite-test init --yes \
33
+ --from-zip ../dist/ragsuite-server-test-1.0.0.zip \
34
+ --install-dir ~/ragsuite-test \
35
+ --mode docker \
36
+ --llm-api-key 'your-key'
37
+
38
+ ragsuite-test start --repo-root ~/ragsuite-test --detach
39
+ # API http://localhost:9090 · Web http://localhost:9091
40
+ ```
41
+
42
+ `init` auto-generates `JWT_SECRET_KEY`, writes `.ragsuite-test/config.json`, checks ports **9090 / 9091 / 5436 / 6382**, and warns if SMTP is skipped (invites/email may fail).
43
+
44
+ ## Install sources
45
+
46
+ | Method | Flag |
47
+ |--------|------|
48
+ | Local ZIP | `--from-zip` (recommended for private testing) |
49
+ | Existing checkout | `--repo-root` |
50
+ | GitHub Release | `--from-release` — not implemented yet |
51
+
52
+ Making GitHub public is **not** required for ZIP installs.
53
+
54
+ ## Config
55
+
56
+ - CLI: `<install>/.ragsuite-test/config.json`
57
+ - Native PID/logs: `<install>/.ragsuite/native/` (Phase 4 scripts)
58
+
59
+ ## Commands
60
+
61
+ | Command | Behavior |
62
+ |---------|----------|
63
+ | `init` | Installer wizard (ZIP/checkout → .env + config) |
64
+ | `start` / `stop` | `scripts/docker-*.sh` or `native-*.sh` |
65
+ | `doctor` | `scripts/doctor.sh` |
66
+ | `logs` / `update` / `version` | As before |
67
+
68
+ ## Publish (testing package only — Phase 12)
69
+
70
+ See [PUBLISH.md](./PUBLISH.md) (full runbook) and [TEST_DISTRIBUTION.md](./TEST_DISTRIBUTION.md) (tester steps + go/no-go).
71
+
72
+ ```bash
73
+ cd cli && npm run prepublish:check
74
+ # Only when someone says "publish now":
75
+ RAGSUITE_TEST_ALLOW_PUBLISH=1 npm publish --access public
76
+ # Or: GitHub Actions → CLI publish (workflow_dispatch, NPM_TOKEN)
77
+ # Optional app ZIP: Actions → Release app ZIP
78
+ ```
79
+
80
+ ## Windows
81
+
82
+ Use WSL or Git Bash (`bash` scripts).
83
+
84
+ ## Tests
85
+
86
+ ```bash
87
+ cd cli && npm test && npm run prepublish:check
88
+ ```
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const major = Number(String(process.versions.node).split('.')[0]);
5
+ if (!Number.isFinite(major) || major < 18) {
6
+ console.error(
7
+ `RAGSuite test CLI requires Node.js 18 or newer. Current: ${process.version}`,
8
+ );
9
+ process.exit(1);
10
+ }
11
+
12
+ const { main } = require('../src/index.js');
13
+
14
+ main()
15
+ .then((code) => {
16
+ process.exit(typeof code === 'number' ? code : 0);
17
+ })
18
+ .catch((err) => {
19
+ console.error(err && err.message ? err.message : String(err));
20
+ process.exit(1);
21
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@nitsan-ai/ragsuite-test",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "RAGSuite testing CLI (ragsuite-test) — install from ZIP or --repo-root checkout. Temporary npm identity before public ragsuite release.",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "ragsuite-test": "./bin/ragsuite-test.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "src/",
13
+ "package.json",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "start": "node src/index.js",
19
+ "test": "node test/smoke.js",
20
+ "test:pack": "node test/pack-assert.js",
21
+ "prepublish:check": "node scripts/prepublish-check.js",
22
+ "prepublishOnly": "node scripts/require-publish-allow.js"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { resolveMode } = require('../utils/config');
5
+ const { resolveRepoRoot } = require('../utils/paths');
6
+ const { runScript } = require('../utils/spawn');
7
+
8
+ const name = 'doctor';
9
+ const summary = 'Run scripts/doctor.sh prerequisite checks';
10
+
11
+ function help() {
12
+ return `Usage: ragsuite-test doctor [options]
13
+
14
+ Runs scripts/doctor.sh. Mode selects which checklist gates the exit code
15
+ (RAGSUITE_MODE / config / --mode). Both mode sections are still printed.
16
+
17
+ Options:
18
+ --mode <docker|native> Gate exit on this mode (default: docker)
19
+ --repo-root <path> Monorepo root
20
+ --dry-run Print the script path and exit 0
21
+ `;
22
+ }
23
+
24
+ function run(ctx) {
25
+ const repoRoot = resolveRepoRoot({
26
+ cwd: ctx.cwd,
27
+ repoRootFlag: ctx.globals.repoRoot,
28
+ });
29
+ const mode = resolveMode({
30
+ flagMode: ctx.globals.mode,
31
+ repoRoot,
32
+ env: ctx.env,
33
+ });
34
+ return runScript(repoRoot, path.join('scripts', 'doctor.sh'), [], {
35
+ dryRun: ctx.globals.dryRun,
36
+ env: { ...ctx.env, RAGSUITE_MODE: mode },
37
+ });
38
+ }
39
+
40
+ module.exports = { name, summary, help, run };
@@ -0,0 +1,255 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { writeConfig, CONFIG_DIR } = require('../utils/config');
6
+ const { ensureRagsuiteDir, resolveRepoRoot, looksLikeRepoRoot } = require('../utils/paths');
7
+ const { assertSupportedInstall, installHint } = require('../utils/distribution');
8
+ const {
9
+ defaultInstallDir,
10
+ extractZipToInstallDir,
11
+ } = require('../utils/zip-install');
12
+ const { detectOs } = require('../utils/os-detect');
13
+ const { printPrereqs, checkPrereqs } = require('../utils/prereqs');
14
+ const { ask, confirm, choose } = require('../utils/prompt');
15
+ const {
16
+ generateJwtSecret,
17
+ generateCiLlmKey,
18
+ isPlaceholderSecret,
19
+ writeInstallEnv,
20
+ } = require('../utils/env-file');
21
+ const { assertPortsFree } = require('../utils/port');
22
+ const { info, warn, error } = require('../utils/log');
23
+
24
+ const name = 'init';
25
+ const summary = 'Installer wizard: ZIP or checkout → .env + config';
26
+
27
+ function help() {
28
+ return `Usage: ragsuite-test init [options]
29
+
30
+ Interactive installer (or --yes for CI) that:
31
+ - detects OS / prerequisites
32
+ - installs from ZIP or configures a checkout
33
+ - writes .ragsuite-test/config.json and .env (JWT auto-generated)
34
+ - checks ports 9090, 9091, 5436, 6382
35
+
36
+ Install sources:
37
+ --from-zip <path> Extract app ZIP into --install-dir
38
+ --repo-root <path> Use an existing monorepo checkout
39
+ --from-release <tag> Not implemented (download ZIP manually)
40
+
41
+ Options:
42
+ --install-dir <path> ZIP target (default: ~/ragsuite-test)
43
+ --mode <docker|native> docker = Docker stack; native = no Docker (needs host Postgres/Redis)
44
+ --llm-api-key <key> CUSTOM_LLM_INTERNAL_API_KEY (--yes)
45
+ --force Overwrite non-empty install dir / existing .env
46
+ --yes, -y Non-interactive (requires --from-zip or --repo-root)
47
+ `;
48
+ }
49
+
50
+ function printSuccess(repoRoot, mode) {
51
+ info('');
52
+ info('=== Install complete ===');
53
+ info(` Install dir : ${repoRoot}`);
54
+ info(` Mode : ${mode}`);
55
+ info(` Config : ${path.join(repoRoot, CONFIG_DIR, 'config.json')}`);
56
+ info('');
57
+ info(' API http://localhost:9090');
58
+ if (mode === 'native') {
59
+ info(' Web http://localhost:9191 (Expo web — native mode)');
60
+ } else {
61
+ info(' Web http://localhost:9091 (Docker nginx)');
62
+ }
63
+ info('');
64
+ info(' Mode reminder: docker needs Docker; native needs host Postgres/Redis (no Docker required).');
65
+ info(' Each install directory is one deployment / one organization.');
66
+ info('');
67
+ info('Next steps:');
68
+ info(` 1. ragsuite-test start --repo-root ${repoRoot}${mode === 'docker' ? ' --detach' : ''}`);
69
+ if (mode === 'native') {
70
+ info(' 2. Open http://localhost:9191 and bootstrap the first admin');
71
+ } else {
72
+ info(' 2. Open http://localhost:9091 and bootstrap the first admin');
73
+ }
74
+ info(` 3. ragsuite-test doctor --repo-root ${repoRoot}`);
75
+ info('');
76
+ info(installHint());
77
+ }
78
+
79
+ async function resolveLlmKey(g, env, interactive) {
80
+ if (g.llmApiKey && !isPlaceholderSecret(g.llmApiKey)) {
81
+ return g.llmApiKey;
82
+ }
83
+ if (env.RAGSUITE_TEST_LLM_API_KEY && !isPlaceholderSecret(env.RAGSUITE_TEST_LLM_API_KEY)) {
84
+ return env.RAGSUITE_TEST_LLM_API_KEY;
85
+ }
86
+ if (g.yes || !interactive) {
87
+ const key = generateCiLlmKey();
88
+ warn(`Using generated smoke LLM key (${key.slice(0, 18)}…). Set a real CUSTOM_LLM_INTERNAL_API_KEY for production-like tests.`);
89
+ return key;
90
+ }
91
+ for (;;) {
92
+ const key = await ask('CUSTOM_LLM_INTERNAL_API_KEY', { yesMode: false });
93
+ if (!isPlaceholderSecret(key)) return key;
94
+ error('Key required and must not start with change-me');
95
+ }
96
+ }
97
+
98
+ async function run(ctx) {
99
+ assertSupportedInstall();
100
+ const g = ctx.globals;
101
+ const interactive = Boolean(process.stdin.isTTY) && !g.yes;
102
+
103
+ if (g.fromRelease) {
104
+ error(
105
+ `--from-release is not implemented. Download a ZIP (private Release asset or local file), then: ragsuite-test init --from-zip <file.zip>`,
106
+ );
107
+ return 1;
108
+ }
109
+
110
+ if (g.fromZip && g.repoRoot) {
111
+ error('Use either --from-zip or --repo-root, not both');
112
+ return 1;
113
+ }
114
+
115
+ const osInfo = detectOs();
116
+ info(`OS: ${osInfo.label} (${osInfo.platform})`);
117
+
118
+ let mode = g.mode || 'docker';
119
+ let fromZip = g.fromZip;
120
+ let repoRootFlag = g.repoRoot;
121
+ let installDir = g.installDir || defaultInstallDir();
122
+
123
+ if (g.yes) {
124
+ if (!fromZip && !repoRootFlag) {
125
+ error('--yes requires --from-zip <path> or --repo-root <path>');
126
+ return 1;
127
+ }
128
+ } else if (interactive && !fromZip && !repoRootFlag) {
129
+ const method = await choose(
130
+ 'Install method:',
131
+ [
132
+ { id: 'zip', label: 'Local ZIP file (--from-zip)' },
133
+ { id: 'checkout', label: 'Existing checkout (--repo-root)' },
134
+ ],
135
+ { defaultIndex: 0 },
136
+ );
137
+ if (method.id === 'zip') {
138
+ fromZip = await ask('Path to app ZIP', { yesMode: false });
139
+ if (!fromZip) {
140
+ error('ZIP path required');
141
+ return 1;
142
+ }
143
+ installDir = await ask('Install directory', {
144
+ defaultValue: defaultInstallDir(),
145
+ yesMode: false,
146
+ });
147
+ } else {
148
+ repoRootFlag = await ask('Path to existing RAGSUITE checkout', { yesMode: false });
149
+ if (!repoRootFlag || !looksLikeRepoRoot(path.resolve(repoRootFlag))) {
150
+ error('Not a valid checkout (need scripts/docker-start.sh)');
151
+ return 1;
152
+ }
153
+ }
154
+
155
+ const modeChoice = await choose(
156
+ 'Run mode (Docker is optional):',
157
+ [
158
+ { id: 'docker', label: 'Docker — if you have Docker Desktop / daemon (default)' },
159
+ { id: 'native', label: 'Native — no Docker; needs Postgres :5436 + Redis :6382 on this machine' },
160
+ ],
161
+ { defaultIndex: 0 },
162
+ );
163
+ mode = modeChoice.id;
164
+ } else if (!fromZip && !repoRootFlag) {
165
+ error('Pass --from-zip / --repo-root, or run in a TTY for the interactive wizard');
166
+ return 1;
167
+ }
168
+
169
+ if (!printPrereqs(mode)) {
170
+ error('Fix prerequisites above and re-run init');
171
+ return 1;
172
+ }
173
+
174
+ const jwtSecret = generateJwtSecret();
175
+ info(`JWT_SECRET_KEY will be auto-generated (…${jwtSecret.slice(-8)})`);
176
+
177
+ const llmKey = await resolveLlmKey(g, ctx.env, interactive);
178
+
179
+ let smtpSkipped = true;
180
+ if (interactive && !g.yes) {
181
+ const configureSmtp = await confirm('Configure SMTP now? (needed for invites/email)', {
182
+ defaultYes: false,
183
+ });
184
+ smtpSkipped = !configureSmtp;
185
+ if (!smtpSkipped) {
186
+ info('After init, edit SMTP_* in .env (SMTP_HOST, SMTP_USER, SMTP_PASSWORD, EMAIL_FROM).');
187
+ }
188
+ } else {
189
+ warn('SMTP skipped (--yes / noninteractive). Invites/email may fail until SMTP_* is set.');
190
+ }
191
+
192
+ // Port checks before mutating install (clearer UX)
193
+ try {
194
+ await assertPortsFree();
195
+ } catch (err) {
196
+ error(err.message);
197
+ return 1;
198
+ }
199
+
200
+ let repoRoot;
201
+ let source = 'checkout';
202
+
203
+ try {
204
+ if (fromZip) {
205
+ info(`Extracting ZIP → ${path.resolve(installDir)}`);
206
+ const result = extractZipToInstallDir(fromZip, {
207
+ installDir,
208
+ force: g.force,
209
+ });
210
+ repoRoot = result.repoRoot;
211
+ source = 'zip';
212
+ info(`Installed app at ${repoRoot}`);
213
+ } else {
214
+ repoRoot = resolveRepoRoot({
215
+ cwd: ctx.cwd,
216
+ repoRootFlag,
217
+ env: ctx.env,
218
+ });
219
+ }
220
+ } catch (err) {
221
+ error(err.message || String(err));
222
+ return 1;
223
+ }
224
+
225
+ ensureRagsuiteDir(repoRoot);
226
+ const cfg = writeConfig(repoRoot, {
227
+ mode,
228
+ source,
229
+ repoRoot,
230
+ installDir: repoRoot,
231
+ });
232
+
233
+ try {
234
+ writeInstallEnv(repoRoot, {
235
+ force: g.force,
236
+ jwtSecret,
237
+ llmApiKey: llmKey,
238
+ smtpSkipped,
239
+ });
240
+ } catch (err) {
241
+ if (err.code === 'ENV_EXISTS') {
242
+ error(err.message);
243
+ return 1;
244
+ }
245
+ error(err.message || String(err));
246
+ return 1;
247
+ }
248
+
249
+ info(`Wrote ${path.join(repoRoot, CONFIG_DIR, 'config.json')}`);
250
+ info(` mode=${cfg.mode} source=${cfg.source}`);
251
+ printSuccess(repoRoot, mode);
252
+ return 0;
253
+ }
254
+
255
+ module.exports = { name, summary, help, run };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { resolveMode } = require('../utils/config');
6
+ const { resolveRepoRoot, nativeLogDir } = require('../utils/paths');
7
+ const { runCommand } = require('../utils/spawn');
8
+ const { error, info } = require('../utils/log');
9
+
10
+ const name = 'logs';
11
+ const summary = 'Follow Docker compose or native process logs';
12
+
13
+ function help() {
14
+ return `Usage: ragsuite-test logs [options]
15
+
16
+ Docker mode: docker compose logs -f (same as root npm run logs).
17
+ Native mode: tail -f .ragsuite/native/*.log when present.
18
+
19
+ Options:
20
+ --mode <docker|native> Override config / RAGSUITE_MODE
21
+ --repo-root <path> Monorepo root
22
+ --dry-run Print the command and exit 0
23
+ `;
24
+ }
25
+
26
+ function run(ctx) {
27
+ const repoRoot = resolveRepoRoot({
28
+ cwd: ctx.cwd,
29
+ repoRootFlag: ctx.globals.repoRoot,
30
+ });
31
+ const mode = resolveMode({
32
+ flagMode: ctx.globals.mode,
33
+ repoRoot,
34
+ env: ctx.env,
35
+ });
36
+
37
+ if (mode === 'native') {
38
+ const dir = nativeLogDir(repoRoot);
39
+ let logs = [];
40
+ if (fs.existsSync(dir)) {
41
+ logs = fs
42
+ .readdirSync(dir)
43
+ .filter((f) => f.endsWith('.log'))
44
+ .map((f) => path.join(dir, f));
45
+ }
46
+ if (logs.length === 0) {
47
+ error(
48
+ `No native log files under ${dir}. Start with mode=native first, or use --mode docker.`,
49
+ );
50
+ return 1;
51
+ }
52
+ info(`Following ${logs.length} native log file(s)…`);
53
+ return runCommand('tail', ['-f', ...logs], {
54
+ cwd: repoRoot,
55
+ dryRun: ctx.globals.dryRun,
56
+ env: ctx.env,
57
+ });
58
+ }
59
+
60
+ return runCommand('docker', ['compose', 'logs', '-f'], {
61
+ cwd: repoRoot,
62
+ dryRun: ctx.globals.dryRun,
63
+ env: ctx.env,
64
+ });
65
+ }
66
+
67
+ module.exports = { name, summary, help, run };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { resolveMode } = require('../utils/config');
5
+ const { resolveRepoRoot } = require('../utils/paths');
6
+ const { runScript } = require('../utils/spawn');
7
+ const { assertApiPortFree } = require('../utils/port');
8
+ const { error } = require('../utils/log');
9
+
10
+ const name = 'start';
11
+ const summary = 'Start stack via docker-start.sh or native-start.sh';
12
+
13
+ function help() {
14
+ return `Usage: ragsuite-test start [options] [-- script-args...]
15
+
16
+ Calls scripts/docker-start.sh (mode=docker) or scripts/native-start.sh (mode=native).
17
+ Does not reimplement compose logic. Extra args after -- are forwarded (e.g. --detach).
18
+
19
+ Options:
20
+ --mode <docker|native> Override config / RAGSUITE_MODE
21
+ --repo-root <path> Monorepo root
22
+ --dry-run Print the script path and exit 0
23
+ --detach, -d Forwarded to docker-start.sh (also accepted without --)
24
+ `;
25
+ }
26
+
27
+ async function run(ctx) {
28
+ const repoRoot = resolveRepoRoot({
29
+ cwd: ctx.cwd,
30
+ repoRootFlag: ctx.globals.repoRoot,
31
+ });
32
+ const mode = resolveMode({
33
+ flagMode: ctx.globals.mode,
34
+ repoRoot,
35
+ env: ctx.env,
36
+ });
37
+
38
+ const scriptName = mode === 'native' ? 'native-start.sh' : 'docker-start.sh';
39
+ const rel = path.join('scripts', scriptName);
40
+ const forward = normalizeStartArgs(ctx.commandArgs);
41
+
42
+ if (!ctx.globals.dryRun) {
43
+ try {
44
+ await assertApiPortFree(ctx.env);
45
+ } catch (err) {
46
+ error(err.message);
47
+ return 1;
48
+ }
49
+ }
50
+
51
+ return runScript(repoRoot, rel, forward, {
52
+ dryRun: ctx.globals.dryRun,
53
+ env: { ...ctx.env, RAGSUITE_MODE: mode },
54
+ });
55
+ }
56
+
57
+ function normalizeStartArgs(args) {
58
+ const out = [];
59
+ for (const a of args) {
60
+ if (a === '--detach' || a === '-d') {
61
+ out.push('--detach');
62
+ continue;
63
+ }
64
+ if (a === '--') continue;
65
+ out.push(a);
66
+ }
67
+ return out;
68
+ }
69
+
70
+ module.exports = { name, summary, help, run };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { resolveMode } = require('../utils/config');
5
+ const { resolveRepoRoot } = require('../utils/paths');
6
+ const { runScript } = require('../utils/spawn');
7
+
8
+ const name = 'stop';
9
+ const summary = 'Stop stack via docker-stop.sh or native-stop.sh';
10
+
11
+ function help() {
12
+ return `Usage: ragsuite-test stop [options]
13
+
14
+ Calls scripts/docker-stop.sh or scripts/native-stop.sh based on mode.
15
+ Never wipes Docker volumes (same as docker-stop.sh).
16
+
17
+ Options:
18
+ --mode <docker|native> Override config / RAGSUITE_MODE
19
+ --repo-root <path> Monorepo root
20
+ --dry-run Print the script path and exit 0
21
+ `;
22
+ }
23
+
24
+ function run(ctx) {
25
+ const repoRoot = resolveRepoRoot({
26
+ cwd: ctx.cwd,
27
+ repoRootFlag: ctx.globals.repoRoot,
28
+ });
29
+ const mode = resolveMode({
30
+ flagMode: ctx.globals.mode,
31
+ repoRoot,
32
+ env: ctx.env,
33
+ });
34
+ const scriptName = mode === 'native' ? 'native-stop.sh' : 'docker-stop.sh';
35
+ return runScript(repoRoot, path.join('scripts', scriptName), [], {
36
+ dryRun: ctx.globals.dryRun,
37
+ env: { ...ctx.env, RAGSUITE_MODE: mode },
38
+ });
39
+ }
40
+
41
+ module.exports = { name, summary, help, run };