@medicine-wheel/app 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.
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * mwsrv — Medicine Wheel Server Launcher
5
+ *
6
+ * Starts the Medicine Wheel Next.js application in local or Docker mode.
7
+ *
8
+ * Usage:
9
+ * mwsrv [options]
10
+ * mwsrv --docker -D /path/to/project
11
+ *
12
+ * Options:
13
+ * --port, -p <port> Port to listen on (default: 3940)
14
+ * --directory, -D <dir> Host directory containing (or for) .mw/store (default: cwd)
15
+ * --docker Run in Docker container
16
+ * --pull Pull Docker image before starting
17
+ * --no-open Do not open browser automatically
18
+ * --help, -h Show this help message
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ const child_process_1 = require("child_process");
55
+ const path = __importStar(require("path"));
56
+ const fs = __importStar(require("fs"));
57
+ const DOCKER_IMAGE = 'jgwill/medicine-wheel:app';
58
+ const DEFAULT_PORT = 3940;
59
+ const CONTAINER_PORT = 3940;
60
+ function parseArgs(argv) {
61
+ const args = argv.slice(2);
62
+ const flags = {};
63
+ const positional = [];
64
+ for (let i = 0; i < args.length; i++) {
65
+ const arg = args[i];
66
+ if (arg === '--') {
67
+ positional.push(...args.slice(i + 1));
68
+ break;
69
+ }
70
+ else if (arg.startsWith('--')) {
71
+ const key = arg.slice(2);
72
+ const next = args[i + 1];
73
+ if (next !== undefined && !next.startsWith('-')) {
74
+ flags[key] = args[++i];
75
+ }
76
+ else {
77
+ flags[key] = true;
78
+ }
79
+ }
80
+ else if (arg.startsWith('-') && arg.length === 2) {
81
+ const key = arg.slice(1);
82
+ const next = args[i + 1];
83
+ if (next !== undefined && !next.startsWith('-')) {
84
+ flags[key] = args[++i];
85
+ }
86
+ else {
87
+ flags[key] = true;
88
+ }
89
+ }
90
+ else {
91
+ positional.push(arg);
92
+ }
93
+ }
94
+ return { flags, positional };
95
+ }
96
+ // ── Helpers ───────────────────────────────────────────────────────
97
+ function resolvePackageRoot() {
98
+ try {
99
+ const scriptDir = path.dirname(fs.realpathSync(process.argv[1]));
100
+ // dist/cli/mwsrv.js → go up two levels to package root
101
+ return path.resolve(scriptDir, '..', '..');
102
+ }
103
+ catch {
104
+ return process.cwd();
105
+ }
106
+ }
107
+ function openBrowser(url) {
108
+ const cmd = process.platform === 'darwin'
109
+ ? 'open'
110
+ : process.platform === 'win32'
111
+ ? 'start'
112
+ : 'xdg-open';
113
+ (0, child_process_1.spawn)(cmd, [url], { detached: true, stdio: 'ignore' }).unref();
114
+ }
115
+ // ── Docker mode ───────────────────────────────────────────────────
116
+ async function runDocker(opts) {
117
+ const { hostDir, port, pull, noOpen } = opts;
118
+ // Resolve store path on host: <dir>/.mw/store
119
+ const storeDir = path.resolve(hostDir, '.mw', 'store');
120
+ fs.mkdirSync(storeDir, { recursive: true });
121
+ if (pull) {
122
+ console.log(`ā¬‡ļø Pulling ${DOCKER_IMAGE}...`);
123
+ await new Promise((resolve, reject) => {
124
+ const p = (0, child_process_1.spawn)('docker', ['pull', DOCKER_IMAGE], { stdio: 'inherit' });
125
+ p.on('exit', (code) => code === 0 ? resolve() : reject(new Error(`docker pull failed: ${code}`)));
126
+ });
127
+ }
128
+ const dockerArgs = [
129
+ 'run', '--rm',
130
+ '-p', `${port}:${CONTAINER_PORT}`,
131
+ '-v', `${storeDir}:/data/store`,
132
+ '-e', `MW_DATA_DIR=/data/store`,
133
+ '-e', `MW_STORAGE_PROVIDER=jsonl`,
134
+ '-e', `PORT=${CONTAINER_PORT}`,
135
+ DOCKER_IMAGE,
136
+ ];
137
+ console.log(`🐳 docker ${dockerArgs.join(' ')}\n`);
138
+ const proc = (0, child_process_1.spawn)('docker', dockerArgs, { stdio: 'inherit' });
139
+ if (!noOpen) {
140
+ setTimeout(() => {
141
+ const url = `http://localhost:${port}`;
142
+ console.log(`\nšŸš€ Opening browser: ${url}\n`);
143
+ openBrowser(url);
144
+ }, 4000);
145
+ }
146
+ const shutdown = () => {
147
+ console.log('\nšŸ‘‹ Shutting down Medicine Wheel...');
148
+ proc.kill();
149
+ process.exit(0);
150
+ };
151
+ process.on('SIGINT', shutdown);
152
+ process.on('SIGTERM', shutdown);
153
+ proc.on('exit', (code) => process.exit(code ?? 0));
154
+ }
155
+ // ── Local mode ────────────────────────────────────────────────────
156
+ function runLocal(opts) {
157
+ const { hostDir, port, noOpen, packageRoot } = opts;
158
+ const storeDir = path.resolve(hostDir, '.mw', 'store');
159
+ fs.mkdirSync(storeDir, { recursive: true });
160
+ // Local mode always runs `next dev`. `next start` requires a production build
161
+ // (.next/) which is not shipped in the published package. For a
162
+ // production-like experience without a source checkout, use --docker instead.
163
+ const nextCmd = 'dev';
164
+ console.log(`🌿 Medicine Wheel — local dev mode (next dev)`);
165
+ console.log(` For a production-built server use: mwsrv --docker`);
166
+ console.log(`šŸ“ Store: ${storeDir}`);
167
+ console.log(`🌐 http://localhost:${port}\n`);
168
+ const env = {
169
+ ...process.env,
170
+ MW_DATA_DIR: storeDir,
171
+ MW_STORAGE_PROVIDER: process.env.MW_STORAGE_PROVIDER ?? 'jsonl',
172
+ PORT: String(port),
173
+ };
174
+ // next binary may be in node_modules/.bin relative to package root
175
+ const nextBin = path.join(packageRoot, 'node_modules', '.bin', 'next');
176
+ const nextExe = fs.existsSync(nextBin) ? nextBin : 'next';
177
+ const proc = (0, child_process_1.spawn)(nextExe, [nextCmd, '-p', String(port)], {
178
+ cwd: packageRoot,
179
+ stdio: 'inherit',
180
+ env,
181
+ });
182
+ if (!noOpen) {
183
+ setTimeout(() => {
184
+ const url = `http://localhost:${port}`;
185
+ console.log(`\nšŸš€ Opening browser: ${url}\n`);
186
+ openBrowser(url);
187
+ }, 5000);
188
+ }
189
+ const shutdown = () => {
190
+ console.log('\nšŸ‘‹ Shutting down Medicine Wheel...');
191
+ proc.kill();
192
+ process.exit(0);
193
+ };
194
+ process.on('SIGINT', shutdown);
195
+ process.on('SIGTERM', shutdown);
196
+ proc.on('exit', (code) => process.exit(code ?? 0));
197
+ }
198
+ // ── Help ──────────────────────────────────────────────────────────
199
+ function showHelp() {
200
+ console.log(`
201
+ \x1b[1m🌿 mwsrv — Medicine Wheel Server Launcher\x1b[0m
202
+
203
+ DESCRIPTION
204
+ Starts the Medicine Wheel Next.js application locally or in Docker.
205
+ In Docker mode, data persists in the host directory's .mw/store folder.
206
+
207
+ USAGE
208
+ mwsrv [options]
209
+
210
+ OPTIONS
211
+ --port, -p <port> Port to listen on (default: ${DEFAULT_PORT})
212
+ --directory, -D <dir> Host directory for .mw/store (default: current dir)
213
+ --docker Run in Docker container (image: ${DOCKER_IMAGE})
214
+ --pull Pull/update Docker image before starting
215
+ --no-open Do not auto-open browser
216
+ --help, -h Show this help
217
+
218
+ EXAMPLES
219
+ # Start locally (uses current directory's .mw/store)
220
+ mwsrv
221
+
222
+ # Start locally with a specific data directory
223
+ mwsrv -D ~/my-research
224
+
225
+ # Start in Docker (recommended for clean environments)
226
+ mwsrv --docker
227
+
228
+ # Docker with custom directory and port
229
+ mwsrv --docker -D ~/my-research --port 4000
230
+
231
+ # Pull latest image then start in Docker
232
+ mwsrv --docker --pull -D ~/my-research
233
+
234
+ ENVIRONMENT
235
+ MW_API_URL Client API URL (default: http://localhost:${DEFAULT_PORT})
236
+ MW_STORAGE_PROVIDER Storage backend: jsonl (default) or postgres
237
+ DATABASE_URL PostgreSQL connection string (when using postgres provider)
238
+ `);
239
+ }
240
+ // ── Main ──────────────────────────────────────────────────────────
241
+ async function main() {
242
+ const { flags } = parseArgs(process.argv);
243
+ if (flags['help'] || flags['h']) {
244
+ showHelp();
245
+ return;
246
+ }
247
+ const port = Number(flags['port'] ?? flags['p'] ?? DEFAULT_PORT);
248
+ const directory = String(flags['directory'] ?? flags['D'] ?? process.cwd());
249
+ const useDocker = Boolean(flags['docker']);
250
+ const pull = Boolean(flags['pull']);
251
+ const noOpen = Boolean(flags['no-open']);
252
+ const hostDir = path.resolve(directory);
253
+ console.log(`🌿 Medicine Wheel${useDocker ? ' [DOCKER]' : ' [LOCAL]'}`);
254
+ console.log(`šŸ“ Directory: ${hostDir}`);
255
+ console.log(`🌐 Port: ${port}\n`);
256
+ if (useDocker) {
257
+ await runDocker({ hostDir, port, pull, noOpen });
258
+ }
259
+ else {
260
+ const packageRoot = resolvePackageRoot();
261
+ runLocal({ hostDir, port, noOpen, packageRoot });
262
+ }
263
+ }
264
+ main().catch((err) => {
265
+ console.error('āŒ Fatal error:', err instanceof Error ? err.message : String(err));
266
+ process.exit(1);
267
+ });
@@ -0,0 +1,15 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ TAG="${1:-app}"
5
+ IMAGE="jgwill/medicine-wheel:${TAG}"
6
+
7
+ echo "🐳 Building ${IMAGE}..."
8
+ docker build -t "${IMAGE}" .
9
+
10
+ echo "šŸ“¤ Pushing ${IMAGE}..."
11
+ docker push "${IMAGE}"
12
+
13
+ echo "āœ… Done: ${IMAGE}"
14
+ echo "Run with:"
15
+ echo " docker run --rm -p 3940:3940 -v /abs/path/to/project/.mw/store:/data/store ${IMAGE}"
@@ -0,0 +1,26 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ # Medicine Wheel Docker Entrypoint
5
+ #
6
+ # The Next.js app is pre-built. Data store is mounted at /data/store.
7
+ # Environment variables:
8
+ # MW_DATA_DIR — path to JSONL store directory (default: /data/store)
9
+ # MW_STORAGE_PROVIDER — storage backend (default: jsonl)
10
+ # PORT — HTTP port (default: 3940)
11
+
12
+ STORE_DIR="${MW_DATA_DIR:-/data/store}"
13
+ PORT="${PORT:-3940}"
14
+
15
+ mkdir -p "$STORE_DIR"
16
+
17
+ echo "🌿 Medicine Wheel"
18
+ echo "šŸ“ Store: $STORE_DIR"
19
+ echo "🌐 http://localhost:$PORT"
20
+ echo ""
21
+
22
+ export MW_DATA_DIR="$STORE_DIR"
23
+ export MW_STORAGE_PROVIDER="${MW_STORAGE_PROVIDER:-jsonl}"
24
+ export PORT="$PORT"
25
+
26
+ exec node_modules/.bin/next start -p "$PORT"