easybuild-nox 1.0.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 +21 -0
- package/README.md +405 -0
- package/bin/easy.js +71 -0
- package/easy.config.js +58 -0
- package/package.json +69 -0
- package/src/commands/analyze.js +203 -0
- package/src/commands/build.js +80 -0
- package/src/commands/clean.js +109 -0
- package/src/commands/config.js +104 -0
- package/src/commands/db.js +423 -0
- package/src/commands/deploy.js +203 -0
- package/src/commands/dev.js +188 -0
- package/src/commands/docker.js +264 -0
- package/src/commands/electron.js +210 -0
- package/src/commands/env.js +302 -0
- package/src/commands/generate.js +556 -0
- package/src/commands/health.js +261 -0
- package/src/commands/info.js +139 -0
- package/src/commands/init.js +794 -0
- package/src/commands/lint.js +111 -0
- package/src/commands/modules.js +333 -0
- package/src/commands/package.js +265 -0
- package/src/commands/proxy.js +73 -0
- package/src/commands/run.js +199 -0
- package/src/commands/test.js +104 -0
- package/src/index.js +30 -0
- package/src/targets/docker.js +201 -0
- package/src/targets/electron.js +157 -0
- package/src/targets/frontend.js +391 -0
- package/src/targets/library.js +164 -0
- package/src/targets/node.js +146 -0
- package/src/utils/config.js +191 -0
- package/src/utils/detector.js +407 -0
- package/src/utils/globalConfig.js +106 -0
- package/src/utils/logger.js +98 -0
- package/src/utils/modules.js +571 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { spawn, execSync } = require('child_process');
|
|
5
|
+
const logger = require('./logger');
|
|
6
|
+
|
|
7
|
+
const EASY_HOME = path.join(os.homedir(), '.easy');
|
|
8
|
+
const SHARED_MODULES = path.join(EASY_HOME, 'modules');
|
|
9
|
+
const MODULES_CACHE = path.join(EASY_HOME, 'cache');
|
|
10
|
+
const MODULES_DB = path.join(EASY_HOME, 'modules.json');
|
|
11
|
+
const ELECTRON_DIR = path.join(EASY_HOME, 'electron');
|
|
12
|
+
|
|
13
|
+
// Special packages that need special handling
|
|
14
|
+
const SPECIAL_PACKAGES = {
|
|
15
|
+
electron: {
|
|
16
|
+
handler: 'installElectron',
|
|
17
|
+
description: 'Electron binary (downloaded separately)',
|
|
18
|
+
},
|
|
19
|
+
'@electron/remote': {
|
|
20
|
+
handler: 'installElectron',
|
|
21
|
+
description: 'Electron remote module',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
class SharedModules {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.initialized = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async init() {
|
|
31
|
+
if (this.initialized) return;
|
|
32
|
+
|
|
33
|
+
await fs.ensureDir(EASY_HOME);
|
|
34
|
+
await fs.ensureDir(SHARED_MODULES);
|
|
35
|
+
await fs.ensureDir(MODULES_CACHE);
|
|
36
|
+
|
|
37
|
+
if (!(await fs.pathExists(MODULES_DB))) {
|
|
38
|
+
await fs.writeJson(MODULES_DB, { modules: {}, lastUpdate: null }, { spaces: 2 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.initialized = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getModulesDb() {
|
|
45
|
+
await this.init();
|
|
46
|
+
return await fs.readJson(MODULES_DB);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async saveModulesDb(db) {
|
|
50
|
+
await this.init();
|
|
51
|
+
await fs.writeJson(MODULES_DB, db, { spaces: 2 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async isModuleInstalled(name, version = '*') {
|
|
55
|
+
await this.init();
|
|
56
|
+
const db = await this.getModulesDb();
|
|
57
|
+
const moduleKey = `${name}@${version}`;
|
|
58
|
+
|
|
59
|
+
if (!db.modules[moduleKey]) return false;
|
|
60
|
+
|
|
61
|
+
const modulePath = path.join(SHARED_MODULES, name);
|
|
62
|
+
return await fs.pathExists(modulePath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async installModule(name, version = '*', options = {}) {
|
|
66
|
+
await this.init();
|
|
67
|
+
const db = await this.getModulesDb();
|
|
68
|
+
const moduleKey = `${name}@${version}`;
|
|
69
|
+
|
|
70
|
+
// Check if already installed
|
|
71
|
+
if (await this.isModuleInstalled(name, version) && !options.force) {
|
|
72
|
+
logger.dim(` ${name}@${version} already installed`);
|
|
73
|
+
return { installed: false, cached: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle special packages like Electron
|
|
77
|
+
if (SPECIAL_PACKAGES[name]) {
|
|
78
|
+
return await this.installSpecialPackage(name, version, options);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Install to shared folder
|
|
82
|
+
logger.info(` Installing ${name}@${version}...`);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Create package.json for the module
|
|
86
|
+
const tempDir = path.join(MODULES_CACHE, `install-${Date.now()}`);
|
|
87
|
+
await fs.ensureDir(tempDir);
|
|
88
|
+
|
|
89
|
+
await fs.writeJson(path.join(tempDir, 'package.json'), {
|
|
90
|
+
name: 'temp-install',
|
|
91
|
+
version: '1.0.0',
|
|
92
|
+
dependencies: {
|
|
93
|
+
[name]: version,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Run npm install in temp directory
|
|
98
|
+
await this.runNpmInstall(tempDir);
|
|
99
|
+
|
|
100
|
+
// Move to shared modules
|
|
101
|
+
const installedPath = path.join(tempDir, 'node_modules', name);
|
|
102
|
+
if (await fs.pathExists(installedPath)) {
|
|
103
|
+
const modulePath = path.join(SHARED_MODULES, name);
|
|
104
|
+
await fs.remove(modulePath);
|
|
105
|
+
await fs.move(installedPath, modulePath);
|
|
106
|
+
|
|
107
|
+
// Update database
|
|
108
|
+
db.modules[moduleKey] = {
|
|
109
|
+
name,
|
|
110
|
+
version,
|
|
111
|
+
installedAt: new Date().toISOString(),
|
|
112
|
+
path: modulePath,
|
|
113
|
+
};
|
|
114
|
+
await this.saveModulesDb(db);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Cleanup temp directory
|
|
118
|
+
await fs.remove(tempDir);
|
|
119
|
+
|
|
120
|
+
logger.dim(` ${name}@${version} installed`);
|
|
121
|
+
return { installed: true, cached: false };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.error(` Failed to install ${name}: ${error.message}`);
|
|
124
|
+
return { installed: false, cached: false, error: error.message };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async installSpecialPackage(name, version, options) {
|
|
129
|
+
await this.init();
|
|
130
|
+
const db = await this.getModulesDb();
|
|
131
|
+
const moduleKey = `${name}@${version}`;
|
|
132
|
+
|
|
133
|
+
logger.info(` Installing ${name} (special handling)...`);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
if (name === 'electron') {
|
|
137
|
+
return await this.installElectron(version, options);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// For other special packages, use regular install
|
|
141
|
+
const tempDir = path.join(MODULES_CACHE, `install-${Date.now()}`);
|
|
142
|
+
await fs.ensureDir(tempDir);
|
|
143
|
+
|
|
144
|
+
await fs.writeJson(path.join(tempDir, 'package.json'), {
|
|
145
|
+
name: 'temp-install',
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
dependencies: { [name]: version },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await this.runNpmInstall(tempDir);
|
|
151
|
+
|
|
152
|
+
const installedPath = path.join(tempDir, 'node_modules', name);
|
|
153
|
+
const modulePath = path.join(SHARED_MODULES, name);
|
|
154
|
+
|
|
155
|
+
if (await fs.pathExists(installedPath)) {
|
|
156
|
+
await fs.remove(modulePath);
|
|
157
|
+
await fs.move(installedPath, modulePath);
|
|
158
|
+
|
|
159
|
+
db.modules[moduleKey] = {
|
|
160
|
+
name,
|
|
161
|
+
version,
|
|
162
|
+
installedAt: new Date().toISOString(),
|
|
163
|
+
path: modulePath,
|
|
164
|
+
};
|
|
165
|
+
await this.saveModulesDb(db);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await fs.remove(tempDir);
|
|
169
|
+
logger.dim(` ${name}@${version} installed`);
|
|
170
|
+
return { installed: true, cached: false };
|
|
171
|
+
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.error(` Failed to install ${name}: ${error.message}`);
|
|
174
|
+
return { installed: false, cached: false, error: error.message };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async installElectron(version, options) {
|
|
179
|
+
await fs.ensureDir(ELECTRON_DIR);
|
|
180
|
+
|
|
181
|
+
const db = await this.getModulesDb();
|
|
182
|
+
const electronVersion = version === '*' ? 'latest' : version;
|
|
183
|
+
const moduleKey = `electron@${electronVersion}`;
|
|
184
|
+
|
|
185
|
+
// Check if electron is already installed
|
|
186
|
+
const electronPath = path.join(ELECTRON_DIR, 'dist', 'electron');
|
|
187
|
+
if (await fs.pathExists(electronPath) && !options.force) {
|
|
188
|
+
logger.dim(' Electron already installed');
|
|
189
|
+
|
|
190
|
+
// Update database
|
|
191
|
+
db.modules[moduleKey] = {
|
|
192
|
+
name: 'electron',
|
|
193
|
+
version: electronVersion,
|
|
194
|
+
installedAt: new Date().toISOString(),
|
|
195
|
+
path: ELECTRON_DIR,
|
|
196
|
+
};
|
|
197
|
+
await this.saveModulesDb(db);
|
|
198
|
+
|
|
199
|
+
return { installed: false, cached: true };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
logger.info(' Downloading Electron binary...');
|
|
203
|
+
logger.dim(' This may take a few minutes...');
|
|
204
|
+
|
|
205
|
+
// Create temp directory for electron installation
|
|
206
|
+
const tempDir = path.join(MODULES_CACHE, `electron-${Date.now()}`);
|
|
207
|
+
await fs.ensureDir(tempDir);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Create package.json for electron
|
|
211
|
+
await fs.writeJson(path.join(tempDir, 'package.json'), {
|
|
212
|
+
name: 'temp-electron',
|
|
213
|
+
version: '1.0.0',
|
|
214
|
+
dependencies: {
|
|
215
|
+
electron: electronVersion,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Install electron with special flags
|
|
220
|
+
await new Promise((resolve, reject) => {
|
|
221
|
+
const child = spawn('npm', ['install', '--ignore-scripts'], {
|
|
222
|
+
cwd: tempDir,
|
|
223
|
+
stdio: 'pipe',
|
|
224
|
+
shell: false,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
child.on('close', (code) => {
|
|
228
|
+
if (code === 0) resolve();
|
|
229
|
+
else reject(new Error('npm install failed'));
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
child.on('error', reject);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Run electron's postinstall to download the binary
|
|
236
|
+
await new Promise((resolve, reject) => {
|
|
237
|
+
const child = spawn('node', ['node_modules/electron/install.js'], {
|
|
238
|
+
cwd: tempDir,
|
|
239
|
+
stdio: 'inherit',
|
|
240
|
+
shell: false,
|
|
241
|
+
env: {
|
|
242
|
+
...process.env,
|
|
243
|
+
ELECTRON_MIRROR: 'https://github.com/nicedoc/electron/releases/download/v',
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
child.on('close', (code) => {
|
|
248
|
+
if (code === 0) resolve();
|
|
249
|
+
else reject(new Error('Electron binary download failed'));
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
child.on('error', reject);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Move to electron directory
|
|
256
|
+
const installedPath = path.join(tempDir, 'node_modules', 'electron');
|
|
257
|
+
if (await fs.pathExists(installedPath)) {
|
|
258
|
+
await fs.remove(ELECTRON_DIR);
|
|
259
|
+
await fs.move(installedPath, ELECTRON_DIR);
|
|
260
|
+
|
|
261
|
+
// Update database
|
|
262
|
+
db.modules[moduleKey] = {
|
|
263
|
+
name: 'electron',
|
|
264
|
+
version: electronVersion,
|
|
265
|
+
installedAt: new Date().toISOString(),
|
|
266
|
+
path: ELECTRON_DIR,
|
|
267
|
+
};
|
|
268
|
+
await this.saveModulesDb(db);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Cleanup
|
|
272
|
+
await fs.remove(tempDir);
|
|
273
|
+
|
|
274
|
+
logger.dim(' Electron installed successfully');
|
|
275
|
+
return { installed: true, cached: false };
|
|
276
|
+
|
|
277
|
+
} catch (error) {
|
|
278
|
+
// Cleanup on error
|
|
279
|
+
await fs.remove(tempDir);
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async installFromPackageJson(packageJsonPath, options = {}) {
|
|
285
|
+
await this.init();
|
|
286
|
+
const pkg = await fs.readJson(packageJsonPath);
|
|
287
|
+
const dependencies = pkg.dependencies || {};
|
|
288
|
+
const devDependencies = pkg.devDependencies || {};
|
|
289
|
+
|
|
290
|
+
const allDeps = { ...dependencies, ...devDependencies };
|
|
291
|
+
const results = { installed: 0, cached: 0, failed: 0 };
|
|
292
|
+
|
|
293
|
+
logger.info('Resolving dependencies from package.json...');
|
|
294
|
+
|
|
295
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
296
|
+
const result = await this.installModule(name, version, options);
|
|
297
|
+
if (result.installed) results.installed++;
|
|
298
|
+
else if (result.cached) results.cached++;
|
|
299
|
+
else results.failed++;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async updateAllModules(options = {}) {
|
|
306
|
+
await this.init();
|
|
307
|
+
const db = await this.getModulesDb();
|
|
308
|
+
const results = { updated: 0, failed: 0 };
|
|
309
|
+
|
|
310
|
+
logger.info('Updating all shared modules...');
|
|
311
|
+
|
|
312
|
+
for (const [moduleKey, moduleInfo] of Object.entries(db.modules)) {
|
|
313
|
+
try {
|
|
314
|
+
logger.info(` Updating ${moduleInfo.name}...`);
|
|
315
|
+
|
|
316
|
+
// Remove old version
|
|
317
|
+
const modulePath = path.join(SHARED_MODULES, moduleInfo.name);
|
|
318
|
+
if (await fs.pathExists(modulePath)) {
|
|
319
|
+
await fs.remove(modulePath);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Reinstall
|
|
323
|
+
const result = await this.installModule(moduleInfo.name, moduleInfo.version, { force: true });
|
|
324
|
+
if (result.installed) results.updated++;
|
|
325
|
+
else results.failed++;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
logger.error(` Failed to update ${moduleInfo.name}: ${error.message}`);
|
|
328
|
+
results.failed++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
db.lastUpdate = new Date().toISOString();
|
|
333
|
+
await this.saveModulesDb(db);
|
|
334
|
+
|
|
335
|
+
return results;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async updateModule(name, options = {}) {
|
|
339
|
+
await this.init();
|
|
340
|
+
const db = await this.getModulesDb();
|
|
341
|
+
|
|
342
|
+
// Find all versions of this module
|
|
343
|
+
const moduleEntries = Object.entries(db.modules).filter(
|
|
344
|
+
([key, info]) => info.name === name
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (moduleEntries.length === 0) {
|
|
348
|
+
logger.warning(`Module ${name} not found in shared modules`);
|
|
349
|
+
return { updated: false };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const [moduleKey, moduleInfo] of moduleEntries) {
|
|
353
|
+
logger.info(` Updating ${moduleInfo.name}@${moduleInfo.version}...`);
|
|
354
|
+
|
|
355
|
+
// Remove old version
|
|
356
|
+
const modulePath = path.join(SHARED_MODULES, moduleInfo.name);
|
|
357
|
+
if (await fs.pathExists(modulePath)) {
|
|
358
|
+
await fs.remove(modulePath);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Reinstall
|
|
362
|
+
await this.installModule(moduleInfo.name, moduleInfo.version, { force: true });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return { updated: true };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async listModules() {
|
|
369
|
+
await this.init();
|
|
370
|
+
const db = await this.getModulesDb();
|
|
371
|
+
return db.modules;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async getModulePath(name) {
|
|
375
|
+
await this.init();
|
|
376
|
+
const modulePath = path.join(SHARED_MODULES, name);
|
|
377
|
+
if (await fs.pathExists(modulePath)) {
|
|
378
|
+
return modulePath;
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async cleanUnused() {
|
|
384
|
+
await this.init();
|
|
385
|
+
const db = await this.getModulesDb();
|
|
386
|
+
const results = { removed: 0, kept: 0 };
|
|
387
|
+
|
|
388
|
+
logger.info('Cleaning unused modules...');
|
|
389
|
+
|
|
390
|
+
// Get all projects that use easy
|
|
391
|
+
const projects = await this.findEasyProjects();
|
|
392
|
+
|
|
393
|
+
// Get all dependencies used by projects
|
|
394
|
+
const usedModules = new Set();
|
|
395
|
+
for (const project of projects) {
|
|
396
|
+
try {
|
|
397
|
+
const pkg = await fs.readJson(path.join(project, 'package.json'));
|
|
398
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
399
|
+
for (const [name] of Object.entries(deps)) {
|
|
400
|
+
usedModules.add(name);
|
|
401
|
+
}
|
|
402
|
+
} catch (e) {
|
|
403
|
+
// Skip invalid projects
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Remove unused modules
|
|
408
|
+
for (const [moduleKey, moduleInfo] of Object.entries(db.modules)) {
|
|
409
|
+
if (!usedModules.has(moduleInfo.name)) {
|
|
410
|
+
const modulePath = path.join(SHARED_MODULES, moduleInfo.name);
|
|
411
|
+
if (await fs.pathExists(modulePath)) {
|
|
412
|
+
await fs.remove(modulePath);
|
|
413
|
+
delete db.modules[moduleKey];
|
|
414
|
+
logger.dim(` Removed ${moduleInfo.name}`);
|
|
415
|
+
results.removed++;
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
results.kept++;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
await this.saveModulesDb(db);
|
|
423
|
+
return results;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async findEasyProjects() {
|
|
427
|
+
const projects = [];
|
|
428
|
+
const homeDir = os.homedir();
|
|
429
|
+
|
|
430
|
+
// Check common locations
|
|
431
|
+
const locations = [
|
|
432
|
+
path.join(homeDir, 'projects'),
|
|
433
|
+
path.join(homeDir, 'dev'),
|
|
434
|
+
path.join(homeDir, 'code'),
|
|
435
|
+
path.join(homeDir, 'workspace'),
|
|
436
|
+
path.join(homeDir, 'repos'),
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
for (const location of locations) {
|
|
440
|
+
if (await fs.pathExists(location)) {
|
|
441
|
+
const items = await fs.readdir(location);
|
|
442
|
+
for (const item of items) {
|
|
443
|
+
const itemPath = path.join(location, item);
|
|
444
|
+
const stat = await fs.stat(itemPath);
|
|
445
|
+
if (stat.isDirectory()) {
|
|
446
|
+
if (await fs.pathExists(path.join(itemPath, 'package.json'))) {
|
|
447
|
+
projects.push(itemPath);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return projects;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async getStats() {
|
|
458
|
+
await this.init();
|
|
459
|
+
const db = await this.getModulesDb();
|
|
460
|
+
const moduleCount = Object.keys(db.modules).length;
|
|
461
|
+
|
|
462
|
+
// Calculate total size
|
|
463
|
+
let totalSize = 0;
|
|
464
|
+
const items = await fs.readdir(SHARED_MODULES);
|
|
465
|
+
for (const item of items) {
|
|
466
|
+
const itemPath = path.join(SHARED_MODULES, item);
|
|
467
|
+
const stat = await fs.stat(itemPath);
|
|
468
|
+
totalSize += stat.size;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
moduleCount,
|
|
473
|
+
totalSize,
|
|
474
|
+
lastUpdate: db.lastUpdate,
|
|
475
|
+
sharedPath: SHARED_MODULES,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async runNpmInstall(cwd) {
|
|
480
|
+
return new Promise((resolve, reject) => {
|
|
481
|
+
const child = spawn('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund'], {
|
|
482
|
+
cwd,
|
|
483
|
+
stdio: 'pipe',
|
|
484
|
+
shell: false,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
child.on('close', (code) => {
|
|
488
|
+
if (code === 0) resolve();
|
|
489
|
+
else reject(new Error('npm install failed'));
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
child.on('error', reject);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
getSharedModulesPath() {
|
|
497
|
+
return SHARED_MODULES;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
getNodeModulesPaths() {
|
|
501
|
+
return [
|
|
502
|
+
SHARED_MODULES,
|
|
503
|
+
path.join(process.cwd(), 'node_modules'),
|
|
504
|
+
];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async linkFromLocal() {
|
|
508
|
+
await this.init();
|
|
509
|
+
const localNodeModules = path.join(process.cwd(), 'node_modules');
|
|
510
|
+
const db = await this.getModulesDb();
|
|
511
|
+
const results = { linked: 0, skipped: 0 };
|
|
512
|
+
|
|
513
|
+
if (!(await fs.pathExists(localNodeModules))) {
|
|
514
|
+
logger.warning('No local node_modules found');
|
|
515
|
+
return results;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
logger.info('Linking local modules to shared folder...');
|
|
519
|
+
|
|
520
|
+
const items = await fs.readdir(localNodeModules);
|
|
521
|
+
for (const item of items) {
|
|
522
|
+
// Skip hidden files and .bin
|
|
523
|
+
if (item.startsWith('.') || item === '.bin') continue;
|
|
524
|
+
|
|
525
|
+
const localPath = path.join(localNodeModules, item);
|
|
526
|
+
const sharedPath = path.join(SHARED_MODULES, item);
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
const stat = await fs.lstat(localPath);
|
|
530
|
+
if (!stat.isDirectory()) continue;
|
|
531
|
+
|
|
532
|
+
// Check if already in shared
|
|
533
|
+
if (await fs.pathExists(sharedPath)) {
|
|
534
|
+
results.skipped++;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Read package.json to get version
|
|
539
|
+
const pkgPath = path.join(localPath, 'package.json');
|
|
540
|
+
let version = '*';
|
|
541
|
+
if (await fs.pathExists(pkgPath)) {
|
|
542
|
+
const pkg = await fs.readJson(pkgPath);
|
|
543
|
+
version = pkg.version || '*';
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Copy to shared folder
|
|
547
|
+
await fs.copy(localPath, sharedPath);
|
|
548
|
+
|
|
549
|
+
// Update database
|
|
550
|
+
const moduleKey = `${item}@${version}`;
|
|
551
|
+
db.modules[moduleKey] = {
|
|
552
|
+
name: item,
|
|
553
|
+
version,
|
|
554
|
+
installedAt: new Date().toISOString(),
|
|
555
|
+
path: sharedPath,
|
|
556
|
+
linkedFromLocal: true,
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
results.linked++;
|
|
560
|
+
logger.dim(` Linked ${item}@${version}`);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
// Skip modules that can't be read
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
await this.saveModulesDb(db);
|
|
567
|
+
return results;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
module.exports = new SharedModules();
|