mongofire 6.5.1 → 6.5.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.
- package/CHANGELOG.md +67 -0
- package/bin/mongofire.cjs +852 -0
- package/dist/bin/mongofire.cjs +851 -1
- package/dist/src/changetrack.js +1 -1
- package/dist/src/connection.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/index.cjs +1 -1
- package/dist/src/plugin.js +1 -1
- package/dist/src/reconcile.js +1 -0
- package/dist/src/state.js +1 -1
- package/dist/src/sync.js +1 -1
- package/dist/src/utils.js +1 -1
- package/dist/types/index.d.ts +133 -6
- package/package.json +14 -8
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ─── Global error handlers (Windows popup fix) ────────────────────────────────
|
|
5
|
+
// Windows pe unhandled error -> native crash popup. Ye handlers ensure karte hain
|
|
6
|
+
// ki har error console me aaye, popup nahi.
|
|
7
|
+
process.on('uncaughtException', (err) => {
|
|
8
|
+
console.error('\n❌ MongoFire CLI Error:', err.message || err);
|
|
9
|
+
if (process.env.MONGOFIRE_DEBUG) console.error(err.stack);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
12
|
+
process.on('unhandledRejection', (reason) => {
|
|
13
|
+
console.error('\n❌ MongoFire CLI Error:', reason?.message || reason);
|
|
14
|
+
if (process.env.MONGOFIRE_DEBUG) console.error(reason?.stack || reason);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { pathToFileURL } = require('url');
|
|
21
|
+
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const command = args[0];
|
|
24
|
+
const flags = new Set(args.slice(1));
|
|
25
|
+
|
|
26
|
+
if (flags.has('--esm') && flags.has('--cjs')) {
|
|
27
|
+
console.error('❌ Use only one: --esm OR --cjs');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Load .env early ─────────────────────────────────────────────────────────
|
|
32
|
+
// .env load karo BEFORE config import — warna process.env.ATLAS_URI etc. empty
|
|
33
|
+
// milte hain jab config file evaluate hoti hai. CJS config template bhi apna
|
|
34
|
+
// dotenv.config() call karta hai, lekin CLI se load karna double-safety hai
|
|
35
|
+
// aur ESM configs ke liye zaroori hai jo import hone pe env read karte hain.
|
|
36
|
+
(function _loadDotenv() {
|
|
37
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
38
|
+
if (!fs.existsSync(envPath)) return;
|
|
39
|
+
try {
|
|
40
|
+
require('dotenv').config({ path: envPath });
|
|
41
|
+
} catch (_) {
|
|
42
|
+
// dotenv not installed — env vars must be set manually in the shell
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
|
|
46
|
+
// ─── TTY Detection (Windows fix) ─────────────────────────────────────────────
|
|
47
|
+
// Windows PowerShell/CMD me process.stdin.isTTY undefined hota hai.
|
|
48
|
+
// Agar TTY nahi hai to interactive prompts crash karte hain -> Windows popup.
|
|
49
|
+
// Solution: non-TTY environment me automatically non-interactive mode use karo.
|
|
50
|
+
const IS_TTY = !!process.stdin.isTTY;
|
|
51
|
+
|
|
52
|
+
// ─── Route commands ───────────────────────────────────────────────────────────
|
|
53
|
+
if (command === 'init') {
|
|
54
|
+
const hasFlags = flags.has('--esm') || flags.has('--cjs') || flags.has('--force') || flags.has('-f');
|
|
55
|
+
|
|
56
|
+
if (!hasFlags && IS_TTY) {
|
|
57
|
+
// Full interactive mode - sirf real TTY pe
|
|
58
|
+
doInitInteractive().catch((err) => {
|
|
59
|
+
console.error('❌ Error:', err.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
} else if (!hasFlags && !IS_TTY) {
|
|
63
|
+
// Non-TTY (Windows piped/scripted) - direct mode with auto-detect
|
|
64
|
+
console.log('ℹ️ Non-interactive mode (no TTY detected)');
|
|
65
|
+
console.log(' Use flags for full control: --cjs or --esm, --force\n');
|
|
66
|
+
doInit({ force: false, moduleSystem: null });
|
|
67
|
+
} else {
|
|
68
|
+
doInit({
|
|
69
|
+
force: flags.has('--force') || flags.has('-f'),
|
|
70
|
+
moduleSystem: flags.has('--esm') ? 'esm' : flags.has('--cjs') ? 'cjs' : null,
|
|
71
|
+
flagForced: flags.has('--esm') || flags.has('--cjs'),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
} else if (command === 'status') {
|
|
76
|
+
doStatus().catch((err) => {
|
|
77
|
+
console.error('❌ Error:', err.message);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
} else if (command === 'clean') {
|
|
82
|
+
const hasDaysFlag = [...flags].some((f) => f.startsWith('--days='));
|
|
83
|
+
if (!hasDaysFlag && IS_TTY) {
|
|
84
|
+
doCleanInteractive().catch((err) => {
|
|
85
|
+
console.error('❌ Error:', err.message);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
doClean().catch((err) => {
|
|
90
|
+
console.error('❌ Error:', err.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
} else if (command === 'conflicts') {
|
|
96
|
+
if (IS_TTY) {
|
|
97
|
+
doConflictsInteractive().catch((err) => {
|
|
98
|
+
console.error('❌ Error:', err.message);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
console.log('ℹ️ conflicts command requires an interactive terminal (TTY).');
|
|
103
|
+
console.log(' Run directly in PowerShell or CMD window.\n');
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
} else if (command === 'reconcile') {
|
|
108
|
+
doReconcile().catch((err) => {
|
|
109
|
+
console.error('❌ Error:', err.message);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
} else {
|
|
114
|
+
showHelp();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Help ─────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
function showHelp() {
|
|
120
|
+
console.log(`
|
|
121
|
+
\uD83D\uDD25 MongoFire CLI v${_getVersion()}
|
|
122
|
+
|
|
123
|
+
npx mongofire init [--force] [--esm|--cjs]
|
|
124
|
+
Setup wizard (interactive if terminal supports it)
|
|
125
|
+
--esm Force ESM templates
|
|
126
|
+
--cjs Force CommonJS templates
|
|
127
|
+
--force Overwrite existing config files
|
|
128
|
+
|
|
129
|
+
npx mongofire status
|
|
130
|
+
Show pending sync operation counts
|
|
131
|
+
|
|
132
|
+
npx mongofire clean [--days=N]
|
|
133
|
+
Delete old synced records (default: 7 days)
|
|
134
|
+
Example: npx mongofire clean --days=3
|
|
135
|
+
|
|
136
|
+
npx mongofire conflicts
|
|
137
|
+
View, retry, or dismiss unresolved conflicts
|
|
138
|
+
|
|
139
|
+
npx mongofire reconcile
|
|
140
|
+
Recover writes lost from crashes
|
|
141
|
+
|
|
142
|
+
Tip: Run with MONGOFIRE_DEBUG=1 for full error stack traces
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function _getVersion() {
|
|
147
|
+
try {
|
|
148
|
+
return require(path.join(__dirname, '..', 'package.json')).version;
|
|
149
|
+
} catch { return '?'; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Inquirer / prompt helper ─────────────────────────────────────────────────
|
|
153
|
+
// Inquirer.js use karo agar installed hai.
|
|
154
|
+
// Fallback: apna readline-based prompter jo Windows pe bhi kaam karta hai.
|
|
155
|
+
|
|
156
|
+
async function _prompt(questions) {
|
|
157
|
+
// Try inquirer first
|
|
158
|
+
try {
|
|
159
|
+
const inquirer = require('inquirer');
|
|
160
|
+
// Test ke liye: inquirer v8 prompt function exist karta hai
|
|
161
|
+
if (typeof inquirer.prompt === 'function') {
|
|
162
|
+
return await inquirer.prompt(questions);
|
|
163
|
+
}
|
|
164
|
+
} catch (_) {
|
|
165
|
+
// inquirer not installed — use readline fallback
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return _readlinePrompt(questions);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function _readlinePrompt(questions) {
|
|
172
|
+
// Windows-safe readline wrapper
|
|
173
|
+
// Key fixes:
|
|
174
|
+
// 1. process.stdin.resume() call before creating interface
|
|
175
|
+
// 2. 'close' event handler to resolve hanging promises
|
|
176
|
+
// 3. Error handler on readline interface
|
|
177
|
+
|
|
178
|
+
process.stdin.resume(); // Windows fix: stdin resume karo
|
|
179
|
+
|
|
180
|
+
const readline = require('readline');
|
|
181
|
+
const rl = readline.createInterface({
|
|
182
|
+
input: process.stdin,
|
|
183
|
+
output: process.stdout,
|
|
184
|
+
terminal: IS_TTY, // Windows fix: explicit terminal flag
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
rl.on('error', () => {}); // Windows fix: suppress readline errors
|
|
188
|
+
|
|
189
|
+
const answers = {};
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
for (const q of questions) {
|
|
193
|
+
answers[q.name] = await _askOne(rl, q, answers);
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
rl.close();
|
|
197
|
+
process.stdin.pause(); // Windows fix: stdin pause after done
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return answers;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function _askOne(rl, q, prevAnswers) {
|
|
204
|
+
return new Promise((resolve) => {
|
|
205
|
+
|
|
206
|
+
// 'close' event = stdin closed unexpectedly (Windows pipe issue)
|
|
207
|
+
// Use default value instead of hanging
|
|
208
|
+
const onClose = () => resolve(q.default ?? '');
|
|
209
|
+
rl.once('close', onClose);
|
|
210
|
+
|
|
211
|
+
const done = (val) => {
|
|
212
|
+
rl.removeListener('close', onClose);
|
|
213
|
+
resolve(val);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (q.type === 'list') {
|
|
217
|
+
const choices = q.choices || [];
|
|
218
|
+
const lines = choices
|
|
219
|
+
.map((c, i) => ` ${i + 1}. ${typeof c === 'object' ? c.name : c}`)
|
|
220
|
+
.join('\n');
|
|
221
|
+
const defIdx = typeof q.default === 'number' ? q.default : 0;
|
|
222
|
+
|
|
223
|
+
const msg = q.message + (typeof q.message === 'function' ? q.message(prevAnswers) : '');
|
|
224
|
+
rl.question(`\n${msg}\n${lines}\nEnter number [${defIdx + 1}]: `, (ans) => {
|
|
225
|
+
const trimmed = ans.trim();
|
|
226
|
+
const idx = trimmed ? Math.max(0, parseInt(trimmed, 10) - 1) : defIdx;
|
|
227
|
+
const choice = choices[idx] ?? choices[defIdx] ?? choices[0];
|
|
228
|
+
done(typeof choice === 'object' ? choice.value : choice);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
} else if (q.type === 'confirm') {
|
|
232
|
+
const defVal = q.default !== false;
|
|
233
|
+
const hint = defVal ? 'Y/n' : 'y/N';
|
|
234
|
+
const msgText = typeof q.message === 'function' ? q.message(prevAnswers) : q.message;
|
|
235
|
+
rl.question(`\n${msgText} (${hint}): `, (ans) => {
|
|
236
|
+
const trimmed = ans.trim().toLowerCase();
|
|
237
|
+
done(trimmed === '' ? defVal : trimmed === 'y' || trimmed === 'yes');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
} else {
|
|
241
|
+
const msgText = typeof q.message === 'function' ? q.message(prevAnswers) : q.message;
|
|
242
|
+
const defHint = q.default != null ? ` (${q.default})` : '';
|
|
243
|
+
rl.question(`\n${msgText}${defHint}: `, (ans) => {
|
|
244
|
+
done(ans.trim() || q.default || '');
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Interactive Init ─────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
async function doInitInteractive() {
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
const exists = fs.existsSync(path.join(cwd, 'mongofire.config.js'));
|
|
255
|
+
|
|
256
|
+
console.log('\n\uD83D\uDD25 MongoFire Setup Wizard\n');
|
|
257
|
+
|
|
258
|
+
const answers = await _prompt([
|
|
259
|
+
{
|
|
260
|
+
type: 'list',
|
|
261
|
+
name: 'moduleSystem',
|
|
262
|
+
message: 'Which module system does your project use?',
|
|
263
|
+
choices: [
|
|
264
|
+
{ name: 'CommonJS (require / module.exports)', value: 'cjs' },
|
|
265
|
+
{ name: 'ESM (import / export)', value: 'esm' },
|
|
266
|
+
{ name: 'Auto-detect from package.json', value: 'auto' },
|
|
267
|
+
],
|
|
268
|
+
default: 0,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: 'confirm',
|
|
272
|
+
name: 'force',
|
|
273
|
+
message: exists
|
|
274
|
+
? 'mongofire.config.js already exists. Overwrite it?'
|
|
275
|
+
: 'Create mongofire.config.js and mongofire.js?',
|
|
276
|
+
default: !exists,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
type: 'confirm',
|
|
280
|
+
name: 'realtime',
|
|
281
|
+
message: 'Enable real-time sync? (Requires Atlas or replica set)',
|
|
282
|
+
default: false,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
type: 'input',
|
|
286
|
+
name: 'collections',
|
|
287
|
+
message: 'Collections to sync (comma-separated)',
|
|
288
|
+
default: 'users,products',
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
type: 'list',
|
|
292
|
+
name: 'syncInterval',
|
|
293
|
+
message: 'Polling interval',
|
|
294
|
+
choices: [
|
|
295
|
+
{ name: '5s (dev / aggressive)', value: 5000 },
|
|
296
|
+
{ name: '15s (balanced)', value: 15000 },
|
|
297
|
+
{ name: '30s (default)', value: 30000 },
|
|
298
|
+
{ name: '60s (conservative)', value: 60000 },
|
|
299
|
+
],
|
|
300
|
+
default: 2,
|
|
301
|
+
},
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
if (!answers.force) {
|
|
305
|
+
console.log('\nSetup cancelled — no files changed.\n');
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const wasAutoDetected = answers.moduleSystem === 'auto';
|
|
310
|
+
const moduleSystem = wasAutoDetected
|
|
311
|
+
? detectModuleSystem(cwd, null)
|
|
312
|
+
: answers.moduleSystem;
|
|
313
|
+
|
|
314
|
+
const collections = String(answers.collections || 'users,products')
|
|
315
|
+
.split(',')
|
|
316
|
+
.map((s) => s.trim())
|
|
317
|
+
.filter(Boolean);
|
|
318
|
+
|
|
319
|
+
doInit({
|
|
320
|
+
force: true,
|
|
321
|
+
moduleSystem,
|
|
322
|
+
autoDetected: wasAutoDetected,
|
|
323
|
+
collections,
|
|
324
|
+
realtime: !!answers.realtime,
|
|
325
|
+
syncInterval: Number(answers.syncInterval) || 30000,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ─── Init ─────────────────────────────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
function doInit(options) {
|
|
332
|
+
const cwd = process.cwd();
|
|
333
|
+
const moduleSystem = detectModuleSystem(cwd, options.moduleSystem);
|
|
334
|
+
// moduleSource:
|
|
335
|
+
// 'auto-detected' — user picked "Auto-detect" in wizard, OR no flag passed
|
|
336
|
+
// 'forced' — user explicitly passed --esm / --cjs flag
|
|
337
|
+
// 'auto-detected (from package.json)' — auto picked esm/cjs from pkg.type
|
|
338
|
+
const moduleSource = options.autoDetected
|
|
339
|
+
? `auto-detected (${moduleSystem} from package.json)`
|
|
340
|
+
: options.flagForced
|
|
341
|
+
? `forced (--${moduleSystem})`
|
|
342
|
+
: 'auto-detected';
|
|
343
|
+
|
|
344
|
+
const envResult = ensureEnvFile(path.join(cwd, '.env'));
|
|
345
|
+
const configResult = writeFileIfNeeded(
|
|
346
|
+
path.join(cwd, 'mongofire.config.js'),
|
|
347
|
+
buildConfigTemplate(moduleSystem, options),
|
|
348
|
+
!!options.force
|
|
349
|
+
);
|
|
350
|
+
const entryResult = writeFileIfNeeded(
|
|
351
|
+
path.join(cwd, 'mongofire.js'),
|
|
352
|
+
buildEntryTemplate(moduleSystem),
|
|
353
|
+
!!options.force
|
|
354
|
+
);
|
|
355
|
+
const hints = collectPackageHints(cwd);
|
|
356
|
+
|
|
357
|
+
printInitSummary({ moduleSystem, moduleSource, force: !!options.force, envResult, configResult, entryResult, hints });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function detectModuleSystem(cwd, manualOverride) {
|
|
361
|
+
if (manualOverride && manualOverride !== 'auto') return manualOverride;
|
|
362
|
+
|
|
363
|
+
const pkg = readPackageJson(cwd);
|
|
364
|
+
if (pkg?.type === 'module') return 'esm';
|
|
365
|
+
if (pkg?.type === 'commonjs') return 'cjs';
|
|
366
|
+
|
|
367
|
+
const probes = [
|
|
368
|
+
'index.js', 'app.js', 'server.js', 'main.js',
|
|
369
|
+
path.join('src', 'index.js'),
|
|
370
|
+
path.join('src', 'main.js'),
|
|
371
|
+
];
|
|
372
|
+
for (const rel of probes) {
|
|
373
|
+
const full = path.join(cwd, rel);
|
|
374
|
+
if (!fs.existsSync(full)) continue;
|
|
375
|
+
const code = fs.readFileSync(full, 'utf8');
|
|
376
|
+
if (/\bimport\s.+from\s+['"]/.test(code) || /\bexport\s+default\b/.test(code)) return 'esm';
|
|
377
|
+
if (/\brequire\(/.test(code) || /\bmodule\.exports\b/.test(code)) return 'cjs';
|
|
378
|
+
}
|
|
379
|
+
return 'cjs';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─── Template builders ────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
function buildConfigTemplate(moduleSystem, opts) {
|
|
385
|
+
const cols = (opts && opts.collections && opts.collections.length)
|
|
386
|
+
? opts.collections.map((c) => ` '${c}',`).join('\n')
|
|
387
|
+
: ` 'users',\n 'products',`;
|
|
388
|
+
const realtime = !!(opts && opts.realtime);
|
|
389
|
+
const syncInterval = (opts && opts.syncInterval) || 30000;
|
|
390
|
+
|
|
391
|
+
if (moduleSystem === 'esm') {
|
|
392
|
+
return [
|
|
393
|
+
'/**',
|
|
394
|
+
' * MongoFire Config (ESM)',
|
|
395
|
+
' *',
|
|
396
|
+
' * dotenv — .env file se ATLAS_URI etc. load karta hai.',
|
|
397
|
+
" * 'dotenv/config' import karna ZAROORI hai taake process.env variables",
|
|
398
|
+
' * is config ke evaluate hone SE PEHLE set ho jayein.',
|
|
399
|
+
' * Install: npm i dotenv',
|
|
400
|
+
' */',
|
|
401
|
+
"import 'dotenv/config';",
|
|
402
|
+
'',
|
|
403
|
+
'export default {',
|
|
404
|
+
" localUri: process.env.LOCAL_URI || 'mongodb://127.0.0.1:27017',",
|
|
405
|
+
' atlasUri: process.env.ATLAS_URI,',
|
|
406
|
+
" dbName: process.env.DB_NAME || 'myapp',",
|
|
407
|
+
'',
|
|
408
|
+
' collections: [',
|
|
409
|
+
cols,
|
|
410
|
+
' ],',
|
|
411
|
+
'',
|
|
412
|
+
' syncInterval: ' + syncInterval + ',',
|
|
413
|
+
' batchSize: 200,',
|
|
414
|
+
" syncOwner: '*',",
|
|
415
|
+
' realtime: ' + realtime + ',',
|
|
416
|
+
'',
|
|
417
|
+
' onSync(result) {',
|
|
418
|
+
' if (result.deleted + result.downloaded + result.uploaded > 0) {',
|
|
419
|
+
' console.log(`[MongoFire] Synced: \u2191${result.uploaded} \u2193${result.downloaded} DEL:${result.deleted}`);',
|
|
420
|
+
' }',
|
|
421
|
+
' },',
|
|
422
|
+
' onError(err) {',
|
|
423
|
+
" console.error('[MongoFire] Sync error:', err.message);",
|
|
424
|
+
' },',
|
|
425
|
+
'};',
|
|
426
|
+
'',
|
|
427
|
+
].join('\n');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return [
|
|
431
|
+
"'use strict';",
|
|
432
|
+
'/**',
|
|
433
|
+
' * MongoFire Config (CommonJS)',
|
|
434
|
+
' *',
|
|
435
|
+
' * dotenv — .env file se ATLAS_URI etc. load karta hai.',
|
|
436
|
+
' * require("dotenv").config() ZAROORI hai taake process.env variables',
|
|
437
|
+
' * is config ke evaluate hone SE PEHLE set ho jayein.',
|
|
438
|
+
' * Install: npm i dotenv',
|
|
439
|
+
' */',
|
|
440
|
+
"try { require('dotenv').config(); } catch (_) {}",
|
|
441
|
+
'',
|
|
442
|
+
'module.exports = {',
|
|
443
|
+
" localUri: process.env.LOCAL_URI || 'mongodb://127.0.0.1:27017',",
|
|
444
|
+
' atlasUri: process.env.ATLAS_URI,',
|
|
445
|
+
" dbName: process.env.DB_NAME || 'myapp',",
|
|
446
|
+
'',
|
|
447
|
+
' collections: [',
|
|
448
|
+
cols,
|
|
449
|
+
' ],',
|
|
450
|
+
'',
|
|
451
|
+
' syncInterval: ' + syncInterval + ',',
|
|
452
|
+
' batchSize: 200,',
|
|
453
|
+
" syncOwner: '*',",
|
|
454
|
+
' realtime: ' + realtime + ',',
|
|
455
|
+
'',
|
|
456
|
+
' onSync(result) {',
|
|
457
|
+
' if (result.deleted + result.downloaded + result.uploaded > 0) {',
|
|
458
|
+
' console.log(`[MongoFire] Synced: \u2191${result.uploaded} \u2193${result.downloaded} DEL:${result.deleted}`);',
|
|
459
|
+
' }',
|
|
460
|
+
' },',
|
|
461
|
+
' onError(err) {',
|
|
462
|
+
" console.error('[MongoFire] Sync error:', err.message);",
|
|
463
|
+
' },',
|
|
464
|
+
'};',
|
|
465
|
+
'',
|
|
466
|
+
].join('\n');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function buildEntryTemplate(moduleSystem) {
|
|
470
|
+
if (moduleSystem === 'esm') {
|
|
471
|
+
return [
|
|
472
|
+
"import 'dotenv/config';",
|
|
473
|
+
"import mongofire from 'mongofire';",
|
|
474
|
+
"import config from './mongofire.config.js';",
|
|
475
|
+
'',
|
|
476
|
+
'export const ready = mongofire.start(config);',
|
|
477
|
+
'',
|
|
478
|
+
"mongofire.on('online', () => console.log('[MongoFire] Online'));",
|
|
479
|
+
"mongofire.on('offline', () => console.log('[MongoFire] Offline \u2014 working locally'));",
|
|
480
|
+
'mongofire.on(\'sync\', (r) => {',
|
|
481
|
+
' if (r.deleted + r.downloaded + r.uploaded > 0) {',
|
|
482
|
+
' console.log(`[MongoFire] Synced: \u2191${r.uploaded} \u2193${r.downloaded} DEL:${r.deleted}`);',
|
|
483
|
+
' }',
|
|
484
|
+
'});',
|
|
485
|
+
'',
|
|
486
|
+
'export { mongofire };',
|
|
487
|
+
'',
|
|
488
|
+
].join('\n');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return [
|
|
492
|
+
"'use strict';",
|
|
493
|
+
"const mongofire = require('mongofire');",
|
|
494
|
+
"const config = require('./mongofire.config');",
|
|
495
|
+
'',
|
|
496
|
+
'const ready = mongofire.start(config);',
|
|
497
|
+
'',
|
|
498
|
+
"mongofire.on('online', () => console.log('[MongoFire] Online'));",
|
|
499
|
+
"mongofire.on('offline', () => console.log('[MongoFire] Offline \u2014 working locally'));",
|
|
500
|
+
'mongofire.on(\'sync\', (r) => {',
|
|
501
|
+
' if (r.deleted + r.downloaded + r.uploaded > 0) {',
|
|
502
|
+
' console.log(`[MongoFire] Synced: \u2191${r.uploaded} \u2193${r.downloaded} DEL:${r.deleted}`);',
|
|
503
|
+
' }',
|
|
504
|
+
'});',
|
|
505
|
+
'',
|
|
506
|
+
'module.exports = { mongofire, ready };',
|
|
507
|
+
'',
|
|
508
|
+
].join('\n');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ─── .env helper ─────────────────────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
function ensureEnvFile(envPath) {
|
|
514
|
+
const defaults = [
|
|
515
|
+
['ATLAS_URI', 'mongodb+srv://USERNAME:PASSWORD@cluster0.xxxxx.mongodb.net/'],
|
|
516
|
+
['LOCAL_URI', 'mongodb://127.0.0.1:27017'],
|
|
517
|
+
['DB_NAME', 'myapp'],
|
|
518
|
+
];
|
|
519
|
+
|
|
520
|
+
if (!fs.existsSync(envPath)) {
|
|
521
|
+
let out = '# MongoFire\n';
|
|
522
|
+
for (const [k, v] of defaults) out += `${k}=${v}\n`;
|
|
523
|
+
fs.writeFileSync(envPath, out, 'utf8');
|
|
524
|
+
return { action: 'created', added: defaults.map(([k]) => k) };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const env = fs.readFileSync(envPath, 'utf8');
|
|
528
|
+
const missing = defaults.filter(([k]) => !new RegExp(`^\\s*${escapeRE(k)}\\s*=`, 'm').test(env));
|
|
529
|
+
if (!missing.length) return { action: 'unchanged', added: [] };
|
|
530
|
+
|
|
531
|
+
let patch = env.endsWith('\n') ? '' : '\n';
|
|
532
|
+
patch += '\n# MongoFire\n';
|
|
533
|
+
for (const [k, v] of missing) patch += `${k}=${v}\n`;
|
|
534
|
+
fs.appendFileSync(envPath, patch, 'utf8');
|
|
535
|
+
return { action: 'updated', added: missing.map(([k]) => k) };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function writeFileIfNeeded(filePath, content, force) {
|
|
539
|
+
const exists = fs.existsSync(filePath);
|
|
540
|
+
if (exists && !force) return { action: 'skipped' };
|
|
541
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
542
|
+
return { action: exists ? 'overwritten' : 'created' };
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function collectPackageHints(cwd) {
|
|
546
|
+
const pkg = readPackageJson(cwd);
|
|
547
|
+
const all = Object.assign({}, pkg?.dependencies, pkg?.devDependencies, pkg?.peerDependencies);
|
|
548
|
+
const hints = [];
|
|
549
|
+
if (!all.mongoose) hints.push('mongoose not found — install: npm i mongoose');
|
|
550
|
+
if (!all.dotenv) hints.push('dotenv not installed — REQUIRED for .env loading: npm i dotenv');
|
|
551
|
+
return hints;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function printInitSummary(info) {
|
|
555
|
+
console.log('');
|
|
556
|
+
printResultLine('.env', info.envResult.action, info.envResult.added);
|
|
557
|
+
printResultLine('mongofire.config.js', info.configResult.action);
|
|
558
|
+
printResultLine('mongofire.js', info.entryResult.action);
|
|
559
|
+
|
|
560
|
+
console.log('\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');
|
|
561
|
+
console.log('\uD83D\uDD25 MongoFire ready (' + info.moduleSystem.toUpperCase() + ', ' + info.moduleSource + ')');
|
|
562
|
+
|
|
563
|
+
if (!info.force && (info.configResult.action === 'skipped' || info.entryResult.action === 'skipped')) {
|
|
564
|
+
console.log('\u2139\uFE0F Existing files kept. Use --force to regenerate.');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
console.log('\nNext steps:');
|
|
568
|
+
console.log(' 1. Set ATLAS_URI in .env');
|
|
569
|
+
console.log(' 2. Install dotenv if not already: npm i dotenv');
|
|
570
|
+
console.log(' 3. Add your collections to mongofire.config.js');
|
|
571
|
+
if (info.moduleSystem === 'esm') {
|
|
572
|
+
console.log(" 4. Schema plugin: userSchema.plugin(mongofire.plugin('users'))");
|
|
573
|
+
console.log(" 5. App entry: import { ready } from './mongofire.js'; await ready;");
|
|
574
|
+
} else {
|
|
575
|
+
console.log(" 4. Schema plugin: UserSchema.plugin(mongofire.plugin('users'))");
|
|
576
|
+
console.log(" 5. App entry: await require('./mongofire').ready");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (info.hints.length) {
|
|
580
|
+
console.log('\nHints:');
|
|
581
|
+
for (const h of info.hints) console.log(' \u26A0\uFE0F ' + h);
|
|
582
|
+
}
|
|
583
|
+
console.log('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function printResultLine(name, action, added) {
|
|
587
|
+
const icon = { created: '\u2705', updated: '\u2705', overwritten: '\u267B\uFE0F', skipped: '\u26A0\uFE0F', unchanged: '\u2713' };
|
|
588
|
+
const label = { created: 'created', updated: 'updated', overwritten: 'overwritten', skipped: 'exists (skipped)', unchanged: 'unchanged' };
|
|
589
|
+
console.log((icon[action] || ' ') + ' ' + name + ' ' + (label[action] || action));
|
|
590
|
+
if (added && added.length) console.log(' + ' + added.join(', '));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ─── Interactive Clean ────────────────────────────────────────────────────────
|
|
594
|
+
|
|
595
|
+
async function doCleanInteractive() {
|
|
596
|
+
console.log('\n\uD83E\uDDF9 MongoFire Clean\n');
|
|
597
|
+
|
|
598
|
+
const answers = await _prompt([
|
|
599
|
+
{
|
|
600
|
+
type: 'list',
|
|
601
|
+
name: 'days',
|
|
602
|
+
message: 'Delete synced records older than:',
|
|
603
|
+
choices: [
|
|
604
|
+
{ name: '1 day (aggressive)', value: 1 },
|
|
605
|
+
{ name: '3 days', value: 3 },
|
|
606
|
+
{ name: '7 days (default)', value: 7 },
|
|
607
|
+
{ name: '14 days', value: 14 },
|
|
608
|
+
{ name: '30 days (conservative)', value: 30 },
|
|
609
|
+
],
|
|
610
|
+
default: 2,
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
type: 'confirm',
|
|
614
|
+
name: 'confirm',
|
|
615
|
+
message: 'Confirm delete? This cannot be undone.',
|
|
616
|
+
default: false,
|
|
617
|
+
},
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
if (!answers.confirm) {
|
|
621
|
+
console.log('\nCancelled — nothing deleted.\n');
|
|
622
|
+
process.exit(0);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
flags.add('--days=' + answers.days);
|
|
626
|
+
await doClean();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ─── Status ───────────────────────────────────────────────────────────────────
|
|
630
|
+
|
|
631
|
+
async function doStatus() {
|
|
632
|
+
const cwd = process.cwd();
|
|
633
|
+
const cfgPath = resolveConfigPath(cwd);
|
|
634
|
+
if (!cfgPath) {
|
|
635
|
+
console.error('❌ mongofire.config.js not found. Run: npx mongofire init');
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const config = await loadConfig(cfgPath, cwd);
|
|
640
|
+
const mongofire = requireMongofire();
|
|
641
|
+
|
|
642
|
+
await mongofire.start(config);
|
|
643
|
+
const s = await mongofire.status();
|
|
644
|
+
|
|
645
|
+
console.log('\n\uD83D\uDCCA MongoFire Status');
|
|
646
|
+
console.log('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');
|
|
647
|
+
console.log(' Online: ' + (s.online ? '\uD83D\uDFE2 Yes' : '\uD83D\uDD34 No (offline)'));
|
|
648
|
+
console.log(' Pending: ' + s.pending + ' total unsynced');
|
|
649
|
+
console.log(' Creates: ' + s.creates);
|
|
650
|
+
console.log(' Updates: ' + s.updates);
|
|
651
|
+
console.log(' Deletes: ' + s.deletes + '\n');
|
|
652
|
+
|
|
653
|
+
await mongofire.stop();
|
|
654
|
+
process.exit(0);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// ─── Clean ────────────────────────────────────────────────────────────────────
|
|
658
|
+
|
|
659
|
+
async function doClean() {
|
|
660
|
+
const daysArg = [...flags].find((f) => f.startsWith('--days='));
|
|
661
|
+
const days = daysArg ? parseInt(daysArg.split('=')[1], 10) : 7;
|
|
662
|
+
|
|
663
|
+
if (isNaN(days) || days < 1) {
|
|
664
|
+
console.error('❌ Invalid --days value. Example: npx mongofire clean --days=7');
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const cwd = process.cwd();
|
|
669
|
+
const cfgPath = resolveConfigPath(cwd);
|
|
670
|
+
if (!cfgPath) {
|
|
671
|
+
console.error('❌ mongofire.config.js not found. Run: npx mongofire init');
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const config = await loadConfig(cfgPath, cwd);
|
|
676
|
+
const mongofire = requireMongofire();
|
|
677
|
+
|
|
678
|
+
await mongofire.start(config);
|
|
679
|
+
console.log('\uD83E\uDDF9 Cleaning records older than ' + days + ' days...');
|
|
680
|
+
const count = await mongofire.clean(days);
|
|
681
|
+
console.log('\u2705 Deleted ' + count + ' old record(s)');
|
|
682
|
+
await mongofire.stop();
|
|
683
|
+
process.exit(0);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ─── Interactive Conflicts ────────────────────────────────────────────────────
|
|
687
|
+
|
|
688
|
+
async function doConflictsInteractive() {
|
|
689
|
+
const cwd = process.cwd();
|
|
690
|
+
const cfgPath = resolveConfigPath(cwd);
|
|
691
|
+
if (!cfgPath) {
|
|
692
|
+
console.error('❌ mongofire.config.js not found. Run: npx mongofire init');
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const config = await loadConfig(cfgPath, cwd);
|
|
697
|
+
const mongofire = requireMongofire();
|
|
698
|
+
|
|
699
|
+
await mongofire.start(config);
|
|
700
|
+
const list = await mongofire.conflicts();
|
|
701
|
+
|
|
702
|
+
if (!list.length) {
|
|
703
|
+
console.log('\n\u2705 No unresolved conflicts.\n');
|
|
704
|
+
await mongofire.stop();
|
|
705
|
+
process.exit(0);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
console.log('\n\u26A0\uFE0F ' + list.length + ' unresolved conflict(s):\n');
|
|
709
|
+
for (const c of list) {
|
|
710
|
+
console.log(' \u2022 ' + c.collection + '/' + c.docId + ' op:' + c.type + ' v' + c.version + ' opId:' + c.opId);
|
|
711
|
+
if (c.lastError) console.log(' Error: ' + c.lastError);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const { action } = await _prompt([
|
|
715
|
+
{
|
|
716
|
+
type: 'list',
|
|
717
|
+
name: 'action',
|
|
718
|
+
message: 'What do you want to do?',
|
|
719
|
+
choices: [
|
|
720
|
+
{ name: 'Retry all (reset to pending, sync will re-attempt)', value: 'retry_all' },
|
|
721
|
+
{ name: 'Dismiss all (discard — accept local state)', value: 'dismiss_all' },
|
|
722
|
+
{ name: 'Pick one to retry', value: 'pick_retry' },
|
|
723
|
+
{ name: 'Pick one to dismiss', value: 'pick_dismiss' },
|
|
724
|
+
{ name: 'Do nothing', value: 'nothing' },
|
|
725
|
+
],
|
|
726
|
+
default: 0,
|
|
727
|
+
},
|
|
728
|
+
]);
|
|
729
|
+
|
|
730
|
+
if (action === 'nothing') {
|
|
731
|
+
console.log('\nNo changes made.\n');
|
|
732
|
+
} else if (action === 'retry_all') {
|
|
733
|
+
for (const c of list) await mongofire.retryConflict(c.opId);
|
|
734
|
+
console.log('\n\u2705 Retried ' + list.length + ' conflict(s).\n');
|
|
735
|
+
} else if (action === 'dismiss_all') {
|
|
736
|
+
for (const c of list) await mongofire.dismissConflict(c.opId);
|
|
737
|
+
console.log('\n\u2705 Dismissed ' + list.length + ' conflict(s).\n');
|
|
738
|
+
} else {
|
|
739
|
+
const { opId } = await _prompt([
|
|
740
|
+
{
|
|
741
|
+
type: 'list',
|
|
742
|
+
name: 'opId',
|
|
743
|
+
message: 'Select conflict:',
|
|
744
|
+
choices: list.map((c) => ({
|
|
745
|
+
name: c.collection + '/' + c.docId + ' ' + c.type + ' v' + c.version,
|
|
746
|
+
value: c.opId,
|
|
747
|
+
})),
|
|
748
|
+
default: 0,
|
|
749
|
+
},
|
|
750
|
+
]);
|
|
751
|
+
if (action === 'pick_retry') {
|
|
752
|
+
await mongofire.retryConflict(opId);
|
|
753
|
+
console.log('\n\u2705 Conflict reset to pending.\n');
|
|
754
|
+
} else {
|
|
755
|
+
await mongofire.dismissConflict(opId);
|
|
756
|
+
console.log('\n\u2705 Conflict dismissed.\n');
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
await mongofire.stop();
|
|
761
|
+
process.exit(0);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// ─── Reconcile ────────────────────────────────────────────────────────────────
|
|
765
|
+
|
|
766
|
+
async function doReconcile() {
|
|
767
|
+
const cwd = process.cwd();
|
|
768
|
+
const cfgPath = resolveConfigPath(cwd);
|
|
769
|
+
if (!cfgPath) {
|
|
770
|
+
console.error('❌ mongofire.config.js not found. Run: npx mongofire init');
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const config = await loadConfig(cfgPath, cwd);
|
|
775
|
+
const mongofire = requireMongofire();
|
|
776
|
+
|
|
777
|
+
await mongofire.start(config);
|
|
778
|
+
console.log('\n\uD83D\uDD0D Running reconciliation scan...\n');
|
|
779
|
+
|
|
780
|
+
const results = await mongofire.reconcile({ verbose: true });
|
|
781
|
+
const total = results.reduce((s, r) => s + (r.totalQueued || 0), 0);
|
|
782
|
+
|
|
783
|
+
console.log('\n\uD83D\uDCCA Reconciliation Results');
|
|
784
|
+
console.log('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');
|
|
785
|
+
for (const r of results) {
|
|
786
|
+
if (r.error) {
|
|
787
|
+
console.log(' \u274C ' + r.collection + ': ' + r.error);
|
|
788
|
+
} else {
|
|
789
|
+
console.log(' ' + r.collection + ': P1=' + r.phase1.queued + ' P2=' + r.phase2.queued + ' queued');
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
console.log('\n Total re-queued: ' + total);
|
|
793
|
+
console.log(total > 0 ? ' \u2705 Lost writes recovered.\n' : ' \u2705 No untracked operations found.\n');
|
|
794
|
+
|
|
795
|
+
await mongofire.stop();
|
|
796
|
+
process.exit(0);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
800
|
+
|
|
801
|
+
function requireMongofire() {
|
|
802
|
+
try {
|
|
803
|
+
return require(path.join(__dirname, '..', 'dist', 'src', 'index.cjs'));
|
|
804
|
+
} catch (_) {
|
|
805
|
+
try {
|
|
806
|
+
return require(path.join(__dirname, '..', 'src', 'index.cjs'));
|
|
807
|
+
} catch (err) {
|
|
808
|
+
console.error('❌ MongoFire load failed:', err.message);
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function resolveConfigPath(cwd) {
|
|
815
|
+
for (const f of ['mongofire.config.js', 'mongofire.config.mjs', 'mongofire.config.cjs']) {
|
|
816
|
+
const full = path.join(cwd, f);
|
|
817
|
+
if (fs.existsSync(full)) return full;
|
|
818
|
+
}
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async function loadConfig(configPath, cwd) {
|
|
823
|
+
const ext = path.extname(configPath).toLowerCase();
|
|
824
|
+
if (ext === '.mjs') return loadESM(configPath);
|
|
825
|
+
if (ext === '.cjs') return require(configPath);
|
|
826
|
+
if (isESMProject(cwd)) return loadESM(configPath);
|
|
827
|
+
try {
|
|
828
|
+
return require(configPath);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
if (err && err.code === 'ERR_REQUIRE_ESM') return loadESM(configPath);
|
|
831
|
+
throw err;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function loadESM(filePath) {
|
|
836
|
+
const mod = await import(pathToFileURL(filePath).href);
|
|
837
|
+
return mod.default || mod;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function readPackageJson(cwd) {
|
|
841
|
+
const p = path.join(cwd, 'package.json');
|
|
842
|
+
if (!fs.existsSync(p)) return null;
|
|
843
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function isESMProject(cwd) {
|
|
847
|
+
return readPackageJson(cwd)?.type === 'module';
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function escapeRE(s) {
|
|
851
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
852
|
+
}
|