@hamak/smart-data-dico 1.9.0 → 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.
package/bin/cli.js CHANGED
@@ -2,8 +2,9 @@
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';
6
- import { spawn } from 'child_process';
5
+ import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs';
6
+ import { homedir } from 'node:os';
7
+ import { spawn, spawnSync } from 'child_process';
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url);
9
10
  const __dirname = dirname(__filename);
@@ -16,6 +17,10 @@ for (let i = 0; i < args.length; i++) {
16
17
  if (args[i] === '--port' && args[i + 1]) { flags.port = args[++i]; }
17
18
  else if (args[i] === '--data-dir' && args[i + 1]) { flags.dataDir = args[++i]; }
18
19
  else if (args[i] === '--no-open') { flags.noOpen = true; }
20
+ else if (args[i] === '--validate') {
21
+ // `--validate [folder]` — folder is optional; defaults to the data dir.
22
+ flags.validate = (args[i + 1] && !args[i + 1].startsWith('-')) ? args[++i] : true;
23
+ }
19
24
  else if (args[i] === '--help' || args[i] === '-h') { flags.help = true; }
20
25
  }
21
26
 
@@ -30,6 +35,8 @@ if (flags.help) {
30
35
  Options:
31
36
  --port <number> Server port (default: 3001)
32
37
  --data-dir <path> Data directory path (default: ./data-dictionaries)
38
+ --validate [path] Validate a project folder and exit (no server).
39
+ Defaults to the data dir. Exit code 1 on errors.
33
40
  --no-open Don't open browser automatically
34
41
  -h, --help Show this help
35
42
 
@@ -37,10 +44,47 @@ if (flags.help) {
37
44
  smart-data-dico
38
45
  smart-data-dico --port 4000
39
46
  smart-data-dico --data-dir ~/my-dictionaries
47
+ smart-data-dico --validate ./my-project
48
+ npx @hamak/smart-data-dico --validate ./my-project
40
49
  `);
41
50
  process.exit(0);
42
51
  }
43
52
 
53
+ // --validate: run the standalone project validator and exit (no server).
54
+ // Mirrors the server's bundled/source dual-mode resolution below.
55
+ if (flags.validate !== undefined) {
56
+ const folder = resolve(
57
+ typeof flags.validate === 'string'
58
+ ? flags.validate
59
+ : (flags.dataDir || process.env.DATA_DIR || './data-dictionaries'),
60
+ );
61
+ const bundledValidator = join(PKG_ROOT, 'backend', 'dist', 'validate.mjs');
62
+ const sourceValidator = join(PKG_ROOT, 'backend', 'src', 'scripts', 'validateDico.ts');
63
+
64
+ let vbin, vargs;
65
+ if (existsSync(bundledValidator)) {
66
+ vbin = process.execPath; // node — bundle has all deps inlined
67
+ vargs = [bundledValidator, '--data-dir', folder];
68
+ } else if (existsSync(sourceValidator)) {
69
+ const tsx = [
70
+ join(PKG_ROOT, 'node_modules', '.bin', 'tsx'),
71
+ join(PKG_ROOT, 'backend', 'node_modules', '.bin', 'tsx'),
72
+ ].find(p => existsSync(p));
73
+ vbin = tsx || 'npx';
74
+ vargs = tsx ? [sourceValidator, '--data-dir', folder] : ['tsx', sourceValidator, '--data-dir', folder];
75
+ } else {
76
+ console.error('Error: validator not found (neither bundled nor source).');
77
+ process.exit(1);
78
+ }
79
+
80
+ const r = spawnSync(vbin, vargs, { cwd: PKG_ROOT, stdio: 'inherit', env: process.env });
81
+ if (r.error) {
82
+ console.error('Failed to run validator:', r.error.message);
83
+ process.exit(1);
84
+ }
85
+ process.exit(r.status ?? 0);
86
+ }
87
+
44
88
  const port = flags.port || process.env.PORT || '3001';
45
89
  const dataDir = resolve(flags.dataDir || process.env.DATA_DIR || './data-dictionaries');
46
90
 
@@ -97,56 +141,86 @@ if (existsSync(bundledServer)) {
97
141
 
98
142
  const frontendDist = join(PKG_ROOT, 'frontend', 'dist');
99
143
 
100
- 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(`
101
156
  Smart Data Dictionary
102
157
 
103
158
  Port: ${port}
104
- Data: ${dataDir}
159
+ Data: ${dir}
105
160
  Profile: ${process.env.PROFILE || 'local'}
106
161
  Frontend: ${existsSync(frontendDist) ? 'bundled' : 'dev (use frontend dev server on :3000)'}
107
162
  `);
108
163
 
109
- const child = spawn(bin, binArgs, {
110
- cwd: PKG_ROOT,
111
- env: {
112
- ...process.env,
113
- PORT: port,
114
- NODE_ENV: 'production',
115
- PROFILE: process.env.PROFILE || 'local',
116
- DATA_DIR: dataDir,
117
- SDD_FRONTEND_DIST: frontendDist,
118
- },
119
- stdio: 'inherit',
120
- });
121
-
122
- child.on('error', (err) => {
123
- console.error('Failed to start server:', err.message);
124
- process.exit(1);
125
- });
126
-
127
- child.on('exit', (code) => process.exit(code || 0));
128
-
129
- // Open browser after short delay
130
- if (!flags.noOpen) {
131
- setTimeout(async () => {
132
- const url = `http://localhost:${port}`;
133
- console.log(`Opening ${url} ...`);
134
- try {
135
- const { exec } = await import('child_process');
136
- const cmd = process.platform === 'darwin' ? 'open' :
137
- process.platform === 'win32' ? 'start' : 'xdg-open';
138
- exec(`${cmd} ${url}`);
139
- } catch {
140
- 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;
141
195
  }
142
- }, 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
+ }
143
215
  }
144
216
 
217
+ startServer(dataDir);
218
+
145
219
  process.on('SIGINT', () => {
146
220
  console.log('\nShutting down...');
147
- child.kill('SIGINT');
221
+ if (currentChild) currentChild.kill('SIGINT');
148
222
  });
149
223
 
150
224
  process.on('SIGTERM', () => {
151
- child.kill('SIGTERM');
225
+ if (currentChild) currentChild.kill('SIGTERM');
152
226
  });