@hamak/smart-data-dico 1.9.1 → 1.9.2

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.
@@ -81365,13 +81365,14 @@ function getConfigSection(section) {
81365
81365
  function setConfigSection(section, value) {
81366
81366
  writeAppConfig({ [section]: value });
81367
81367
  }
81368
- var APP_DIR, CONFIG_FILE, STORAGE_DIR, CONVERSATIONS_DIR, PROMPTS_DIR, LEGACY_CONFIG, loosePermsWarned;
81368
+ var APP_DIR, CONFIG_FILE, ACTIVE_PROJECT_FILE, STORAGE_DIR, CONVERSATIONS_DIR, PROMPTS_DIR, LEGACY_CONFIG, loosePermsWarned;
81369
81369
  var init_appDir = __esm({
81370
81370
  "backend/src/utils/appDir.ts"() {
81371
81371
  "use strict";
81372
81372
  init_logger();
81373
81373
  APP_DIR = path8.join(os.homedir(), ".dico-app");
81374
81374
  CONFIG_FILE = path8.join(APP_DIR, "dico-app.json");
81375
+ ACTIVE_PROJECT_FILE = path8.join(APP_DIR, "active-project");
81375
81376
  STORAGE_DIR = path8.join(APP_DIR, "storage");
81376
81377
  CONVERSATIONS_DIR = path8.join(STORAGE_DIR, "conversations");
81377
81378
  PROMPTS_DIR = path8.join(STORAGE_DIR, "prompts");
@@ -161603,7 +161604,31 @@ init_config();
161603
161604
  import fs2 from "fs";
161604
161605
  import path9 from "path";
161605
161606
  import os2 from "os";
161607
+ init_appDir();
161608
+ init_logger();
161606
161609
  var router25 = (0, import_express26.Router)();
161610
+ var RESTART_EXIT_CODE = 75;
161611
+ function applyProjectSwitch(req, res, dataDir, message) {
161612
+ if (process.env.SDD_MANAGED === "1") {
161613
+ try {
161614
+ fs2.mkdirSync(path9.dirname(ACTIVE_PROJECT_FILE), { recursive: true });
161615
+ fs2.writeFileSync(ACTIVE_PROJECT_FILE, dataDir, "utf-8");
161616
+ } catch (e) {
161617
+ logger.error(`Project switch: failed to persist target dir: ${e}`);
161618
+ res.status(500).json({ message: "Failed to switch project (could not persist target)." });
161619
+ return;
161620
+ }
161621
+ res.json({ message, data: { path: dataDir, name: path9.basename(path9.dirname(dataDir)) }, restarting: true });
161622
+ logger.info(`Project switch \u2192 restarting to load ${dataDir}`);
161623
+ setTimeout(() => process.exit(RESTART_EXIT_CODE), 250);
161624
+ return;
161625
+ }
161626
+ config.dataDir = dataDir;
161627
+ const roots = req.app.__workspaceRoots;
161628
+ if (roots) roots.set("dictionaries", dataDir);
161629
+ res.json({ message, data: { path: dataDir, name: path9.basename(path9.dirname(dataDir)) }, restarting: false });
161630
+ }
161631
+ __name(applyProjectSwitch, "applyProjectSwitch");
161607
161632
  router25.get("/api/filesystem/browse", (req, res) => {
161608
161633
  if (config.profile !== "local") {
161609
161634
  return res.status(403).json({ message: "Filesystem browsing is only available in local mode" });
@@ -161668,10 +161693,7 @@ router25.post("/api/project/open", authorizeJwt(["admin" /* ADMIN */]), (req, re
161668
161693
  message: `No dico.config.json found at ${resolved}. Use /api/project/init to create one.`
161669
161694
  });
161670
161695
  }
161671
- config.dataDir = dataDir;
161672
- const roots = req.app.__workspaceRoots;
161673
- if (roots) roots.set("dictionaries", dataDir);
161674
- res.json({ message: `Project opened: ${dataDir}`, data: { path: dataDir, name: path9.basename(path9.dirname(dataDir)) } });
161696
+ applyProjectSwitch(req, res, dataDir, `Project opened: ${dataDir}`);
161675
161697
  });
161676
161698
  router25.post("/api/project/close", authorizeJwt(["admin" /* ADMIN */]), (req, res) => {
161677
161699
  if (config.profile !== "local") {
@@ -161679,10 +161701,7 @@ router25.post("/api/project/close", authorizeJwt(["admin" /* ADMIN */]), (req, r
161679
161701
  }
161680
161702
  const emptyDir = path9.join(os2.tmpdir(), "smart-data-dico-closed");
161681
161703
  if (!fs2.existsSync(emptyDir)) fs2.mkdirSync(emptyDir, { recursive: true });
161682
- config.dataDir = emptyDir;
161683
- const roots = req.app.__workspaceRoots;
161684
- if (roots) roots.set("dictionaries", emptyDir);
161685
- res.json({ message: "Project closed" });
161704
+ applyProjectSwitch(req, res, emptyDir, "Project closed");
161686
161705
  });
161687
161706
  router25.post("/api/project/init", authorizeJwt(["admin" /* ADMIN */]), (req, res) => {
161688
161707
  if (config.profile !== "local") {
@@ -161705,10 +161724,7 @@ router25.post("/api/project/init", authorizeJwt(["admin" /* ADMIN */]), (req, re
161705
161724
  if (!fs2.existsSync(stereotypesPath)) {
161706
161725
  fs2.writeFileSync(stereotypesPath, "[]", "utf-8");
161707
161726
  }
161708
- config.dataDir = dataDir;
161709
- const roots = req.app.__workspaceRoots;
161710
- if (roots) roots.set("dictionaries", dataDir);
161711
- res.json({ message: `Project initialized and opened: ${dataDir}`, data: { path: dataDir } });
161727
+ applyProjectSwitch(req, res, dataDir, `Project initialized and opened: ${dataDir}`);
161712
161728
  } catch (e) {
161713
161729
  res.status(500).json({ message: `Failed to initialize project: ${e}` });
161714
161730
  }
package/bin/cli.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  import { fileURLToPath } from 'url';
4
4
  import { dirname, join, resolve } from 'node:path';
5
- import { existsSync, mkdirSync, cpSync, writeFileSync } from 'fs';
5
+ import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs';
6
+ import { homedir } from 'node:os';
6
7
  import { spawn, spawnSync } from 'child_process';
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url);
@@ -140,56 +141,86 @@ if (existsSync(bundledServer)) {
140
141
 
141
142
  const frontendDist = join(PKG_ROOT, 'frontend', 'dist');
142
143
 
143
- console.log(`
144
+ // In-app "Open project" can't hot-swap the boot-time data dir, so the server
145
+ // persists the new project here and exits with RESTART_EXIT_CODE; we respawn
146
+ // it with DATA_DIR set to that path. SDD_MANAGED=1 tells the server it's safe
147
+ // to use this restart path.
148
+ const RESTART_EXIT_CODE = 75;
149
+ const ACTIVE_PROJECT_FILE = join(homedir(), '.dico-app', 'active-project');
150
+
151
+ let currentChild = null;
152
+ let browserOpened = false;
153
+
154
+ function startServer(dir) {
155
+ console.log(`
144
156
  Smart Data Dictionary
145
157
 
146
158
  Port: ${port}
147
- Data: ${dataDir}
159
+ Data: ${dir}
148
160
  Profile: ${process.env.PROFILE || 'local'}
149
161
  Frontend: ${existsSync(frontendDist) ? 'bundled' : 'dev (use frontend dev server on :3000)'}
150
162
  `);
151
163
 
152
- const child = spawn(bin, binArgs, {
153
- cwd: PKG_ROOT,
154
- env: {
155
- ...process.env,
156
- PORT: port,
157
- NODE_ENV: 'production',
158
- PROFILE: process.env.PROFILE || 'local',
159
- DATA_DIR: dataDir,
160
- SDD_FRONTEND_DIST: frontendDist,
161
- },
162
- stdio: 'inherit',
163
- });
164
-
165
- child.on('error', (err) => {
166
- console.error('Failed to start server:', err.message);
167
- process.exit(1);
168
- });
169
-
170
- child.on('exit', (code) => process.exit(code || 0));
171
-
172
- // Open browser after short delay
173
- if (!flags.noOpen) {
174
- setTimeout(async () => {
175
- const url = `http://localhost:${port}`;
176
- console.log(`Opening ${url} ...`);
177
- try {
178
- const { exec } = await import('child_process');
179
- const cmd = process.platform === 'darwin' ? 'open' :
180
- process.platform === 'win32' ? 'start' : 'xdg-open';
181
- exec(`${cmd} ${url}`);
182
- } catch {
183
- console.log(`Open ${url} in your browser`);
164
+ const child = spawn(bin, binArgs, {
165
+ cwd: PKG_ROOT,
166
+ env: {
167
+ ...process.env,
168
+ PORT: port,
169
+ NODE_ENV: 'production',
170
+ PROFILE: process.env.PROFILE || 'local',
171
+ DATA_DIR: dir,
172
+ SDD_FRONTEND_DIST: frontendDist,
173
+ SDD_MANAGED: '1',
174
+ },
175
+ stdio: 'inherit',
176
+ });
177
+ currentChild = child;
178
+
179
+ child.on('error', (err) => {
180
+ console.error('Failed to start server:', err.message);
181
+ process.exit(1);
182
+ });
183
+
184
+ child.on('exit', (code) => {
185
+ if (code === RESTART_EXIT_CODE) {
186
+ // Project switch requested — read the new dir and respawn.
187
+ let nextDir = dir;
188
+ try {
189
+ const persisted = readFileSync(ACTIVE_PROJECT_FILE, 'utf-8').trim();
190
+ if (persisted) nextDir = persisted;
191
+ } catch { /* keep current dir */ }
192
+ console.log(`\nSwitching project ${nextDir}\n`);
193
+ startServer(nextDir);
194
+ return;
184
195
  }
185
- }, 3000);
196
+ process.exit(code || 0);
197
+ });
198
+
199
+ // Open the browser once, on the first start only (not on project-switch restarts).
200
+ if (!flags.noOpen && !browserOpened) {
201
+ browserOpened = true;
202
+ setTimeout(async () => {
203
+ const url = `http://localhost:${port}`;
204
+ console.log(`Opening ${url} ...`);
205
+ try {
206
+ const { exec } = await import('child_process');
207
+ const cmd = process.platform === 'darwin' ? 'open' :
208
+ process.platform === 'win32' ? 'start' : 'xdg-open';
209
+ exec(`${cmd} ${url}`);
210
+ } catch {
211
+ console.log(`Open ${url} in your browser`);
212
+ }
213
+ }, 3000);
214
+ }
186
215
  }
187
216
 
217
+ startServer(dataDir);
218
+
188
219
  process.on('SIGINT', () => {
189
220
  console.log('\nShutting down...');
190
- child.kill('SIGINT');
221
+ if (currentChild) currentChild.kill('SIGINT');
191
222
  });
192
223
 
193
224
  process.on('SIGTERM', () => {
194
- child.kill('SIGTERM');
225
+ if (currentChild) currentChild.kill('SIGTERM');
195
226
  });