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.
@@ -0,0 +1,111 @@
1
+ const { Command } = require('commander');
2
+ const { spawn } = require('child_process');
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const logger = require('../utils/logger');
6
+ const ProjectDetector = require('../utils/detector');
7
+
8
+ const lintCommand = new Command('lint')
9
+ .description('Run linting and formatting')
10
+ .option('-f, --fix', 'Auto-fix issues', false)
11
+ .option('-p, --prettier', 'Run Prettier', false)
12
+ .option('-e, --eslint', 'Run ESLint', false)
13
+ .option('-a, --all', 'Run all linters', false)
14
+ .action(async (options) => {
15
+ logger.header('🔍 Linting Project');
16
+
17
+ const detector = new ProjectDetector();
18
+
19
+ try {
20
+ const projectInfo = await detector.detect();
21
+
22
+ if (options.all || (!options.prettier && !options.eslint)) {
23
+ // Run all linters
24
+ await runESLint(projectInfo, options);
25
+ await runPrettier(projectInfo, options);
26
+ } else {
27
+ if (options.eslint) await runESLint(projectInfo, options);
28
+ if (options.prettier) await runPrettier(projectInfo, options);
29
+ }
30
+
31
+ logger.success('Linting completed!');
32
+ } catch (error) {
33
+ logger.error(`Linting failed: ${error.message}`);
34
+ process.exit(1);
35
+ }
36
+ });
37
+
38
+ async function runESLint(projectInfo, options) {
39
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
40
+ const deps = {
41
+ ...pkg.dependencies,
42
+ ...pkg.devDependencies,
43
+ };
44
+
45
+ if (!deps.eslint) {
46
+ logger.dim('ESLint not installed, skipping...');
47
+ return;
48
+ }
49
+
50
+ logger.info('Running ESLint...');
51
+
52
+ const args = ['eslint', '.'];
53
+ if (options.fix) args.push('--fix');
54
+
55
+ const child = spawn('npx', args, {
56
+ stdio: 'inherit',
57
+ shell: true,
58
+ });
59
+
60
+ return new Promise((resolve, reject) => {
61
+ child.on('close', (code) => {
62
+ if (code === 0) {
63
+ logger.success('ESLint passed!');
64
+ resolve();
65
+ } else if (code === 1) {
66
+ logger.warning('ESLint found issues');
67
+ resolve(); // Don't fail, just warn
68
+ } else {
69
+ reject(new Error('ESLint failed'));
70
+ }
71
+ });
72
+ });
73
+ }
74
+
75
+ async function runPrettier(projectInfo, options) {
76
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
77
+ const deps = {
78
+ ...pkg.dependencies,
79
+ ...pkg.devDependencies,
80
+ };
81
+
82
+ if (!deps.prettier) {
83
+ logger.dim('Prettier not installed, skipping...');
84
+ return;
85
+ }
86
+
87
+ logger.info('Running Prettier...');
88
+
89
+ const args = ['prettier', '--write', '.'];
90
+ if (!options.fix) {
91
+ args[2] = '--check';
92
+ }
93
+
94
+ const child = spawn('npx', args, {
95
+ stdio: 'inherit',
96
+ shell: true,
97
+ });
98
+
99
+ return new Promise((resolve, reject) => {
100
+ child.on('close', (code) => {
101
+ if (code === 0) {
102
+ logger.success('Prettier passed!');
103
+ resolve();
104
+ } else {
105
+ reject(new Error('Prettier failed'));
106
+ }
107
+ });
108
+ });
109
+ }
110
+
111
+ module.exports = lintCommand;
@@ -0,0 +1,333 @@
1
+ const { Command } = require('commander');
2
+ const path = require('path');
3
+ const fs = require('fs-extra');
4
+ const { spawn } = require('child_process');
5
+ const logger = require('../utils/logger');
6
+ const sharedModules = require('../utils/modules');
7
+
8
+ const modulesCommand = new Command('modules')
9
+ .description('Manage shared modules (saves disk space!)')
10
+ .option('-a, --action <action>', 'Action (install, update, list, clean, sync, stats)', 'list')
11
+ .option('-n, --name <name>', 'Module name for specific operations')
12
+ .option('-f, --force', 'Force reinstall/update', false)
13
+ .action(async (options) => {
14
+ logger.header('📦 Shared Modules Manager');
15
+
16
+ try {
17
+ await sharedModules.init();
18
+
19
+ // Check if using sub-commands (handled by commander)
20
+ if (options.args && options.args.length > 0) {
21
+ return; // Let commander handle sub-commands
22
+ }
23
+
24
+ const actions = {
25
+ install: modulesInstall,
26
+ update: modulesUpdate,
27
+ list: modulesList,
28
+ clean: modulesClean,
29
+ sync: modulesSync,
30
+ stats: modulesStats,
31
+ };
32
+
33
+ const action = actions[options.action];
34
+ if (!action) {
35
+ logger.error(`Unknown action: ${options.action}`);
36
+ process.exit(1);
37
+ }
38
+
39
+ await action(options);
40
+ } catch (error) {
41
+ logger.error(`Modules operation failed: ${error.message}`);
42
+ process.exit(1);
43
+ }
44
+ });
45
+
46
+ async function modulesInstall(options) {
47
+ logger.section('Installing Modules');
48
+
49
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
50
+
51
+ if (!(await fs.pathExists(packageJsonPath))) {
52
+ logger.error('No package.json found in current directory');
53
+ process.exit(1);
54
+ }
55
+
56
+ if (options.name) {
57
+ // Install specific module
58
+ logger.info(`Installing ${options.name}...`);
59
+ const result = await sharedModules.installModule(options.name, '*', { force: options.force });
60
+
61
+ if (result.installed) {
62
+ logger.success(`${options.name} installed to shared modules`);
63
+ } else if (result.cached) {
64
+ logger.info(`${options.name} already exists in shared modules`);
65
+ } else {
66
+ logger.error(`Failed to install ${options.name}`);
67
+ }
68
+ } else {
69
+ // Install all from package.json
70
+ const results = await sharedModules.installFromPackageJson(packageJsonPath, { force: options.force });
71
+
72
+ logger.info('');
73
+ logger.section('Installation Summary');
74
+ logger.success(`Newly installed: ${results.installed}`);
75
+ logger.info(`Already cached: ${results.cached}`);
76
+ if (results.failed > 0) {
77
+ logger.error(`Failed: ${results.failed}`);
78
+ }
79
+
80
+ // Create symlink in project
81
+ await createModuleSymlink();
82
+ }
83
+ }
84
+
85
+ async function modulesUpdate(options) {
86
+ logger.section('Updating Modules');
87
+
88
+ if (options.name) {
89
+ // Update specific module
90
+ logger.info(`Updating ${options.name}...`);
91
+ const result = await sharedModules.updateModule(options.name, { force: options.force });
92
+
93
+ if (result.updated) {
94
+ logger.success(`${options.name} updated successfully`);
95
+ }
96
+ } else {
97
+ // Update all modules
98
+ const results = await sharedModules.updateAllModules({ force: options.force });
99
+
100
+ logger.info('');
101
+ logger.section('Update Summary');
102
+ logger.success(`Updated: ${results.updated}`);
103
+ if (results.failed > 0) {
104
+ logger.error(`Failed: ${results.failed}`);
105
+ }
106
+ }
107
+ }
108
+
109
+ async function modulesList(options) {
110
+ const modules = await sharedModules.listModules();
111
+ const moduleList = Object.values(modules);
112
+
113
+ if (moduleList.length === 0) {
114
+ logger.info('No shared modules installed yet');
115
+ logger.info('Run: easy modules install');
116
+ return;
117
+ }
118
+
119
+ logger.section('Shared Modules');
120
+
121
+ // Group by first letter
122
+ const grouped = {};
123
+ for (const mod of moduleList) {
124
+ const firstLetter = mod.name[0].toUpperCase();
125
+ if (!grouped[firstLetter]) grouped[firstLetter] = [];
126
+ grouped[firstLetter].push(mod);
127
+ }
128
+
129
+ // Display grouped
130
+ for (const [letter, mods] of Object.entries(grouped).sort()) {
131
+ logger.bold(`\n${letter}:`);
132
+ for (const mod of mods) {
133
+ logger.dim(` ${mod.name}@${mod.version}`);
134
+ }
135
+ }
136
+
137
+ logger.info('');
138
+ logger.dim(`Total: ${moduleList.length} modules`);
139
+ }
140
+
141
+ async function modulesClean(options) {
142
+ logger.section('Cleaning Unused Modules');
143
+
144
+ const results = await sharedModules.cleanUnused();
145
+
146
+ logger.info('');
147
+ logger.section('Clean Summary');
148
+ logger.success(`Removed: ${results.removed} modules`);
149
+ logger.info(`Kept: ${results.kept} modules`);
150
+ }
151
+
152
+ async function modulesSync(options) {
153
+ logger.section('Syncing Project Dependencies');
154
+
155
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
156
+
157
+ if (!(await fs.pathExists(packageJsonPath))) {
158
+ logger.error('No package.json found in current directory');
159
+ process.exit(1);
160
+ }
161
+
162
+ // Install all dependencies from package.json
163
+ const results = await sharedModules.installFromPackageJson(packageJsonPath, { force: options.force });
164
+
165
+ // Create symlink
166
+ await createModuleSymlink();
167
+
168
+ logger.info('');
169
+ logger.section('Sync Summary');
170
+ logger.success(`Synced ${results.installed + results.cached} modules`);
171
+ logger.info('Project now uses shared modules');
172
+ }
173
+
174
+ async function modulesStats(options) {
175
+ const stats = await sharedModules.getStats();
176
+
177
+ logger.section('Shared Modules Statistics');
178
+
179
+ logger.info(`Modules installed: ${stats.moduleCount}`);
180
+ logger.info(`Total disk usage: ${formatSize(stats.totalSize)}`);
181
+ logger.info(`Shared location: ${stats.sharedPath}`);
182
+
183
+ if (stats.lastUpdate) {
184
+ logger.info(`Last updated: ${new Date(stats.lastUpdate).toLocaleString()}`);
185
+ }
186
+
187
+ // Calculate savings
188
+ const estimatedPerProject = stats.moduleCount * 2 * 1024 * 1024; // ~2MB per module average
189
+ const potentialSavings = estimatedPerProject - stats.totalSize;
190
+
191
+ if (potentialSavings > 0) {
192
+ logger.info('');
193
+ logger.success(`Estimated disk savings: ${formatSize(potentialSavings)}`);
194
+ logger.dim('(vs having modules in each project)');
195
+ }
196
+ }
197
+
198
+ async function modulesLink(options) {
199
+ logger.section('Linking Local Modules');
200
+
201
+ const results = await sharedModules.linkFromLocal();
202
+
203
+ logger.info('');
204
+ logger.section('Link Summary');
205
+ logger.success(`Linked: ${results.linked} modules`);
206
+ logger.info(`Skipped (already exists): ${results.skipped} modules`);
207
+ }
208
+
209
+ async function modulesOpen(options) {
210
+ const modulesPath = sharedModules.getSharedModulesPath();
211
+
212
+ logger.info(`Opening: ${modulesPath}`);
213
+
214
+ // Open in file manager based on platform
215
+ const platform = process.platform;
216
+ let command;
217
+
218
+ if (platform === 'darwin') {
219
+ command = 'open';
220
+ } else if (platform === 'win32') {
221
+ command = 'explorer';
222
+ } else {
223
+ command = 'xdg-open';
224
+ }
225
+
226
+ try {
227
+ spawn(command, [modulesPath], {
228
+ stdio: 'ignore',
229
+ detached: true
230
+ }).unref();
231
+ logger.success('Opened modules folder!');
232
+ } catch (error) {
233
+ logger.dim(`Could not open folder automatically`);
234
+ logger.info(`Manually navigate to: ${modulesPath}`);
235
+ }
236
+ }
237
+
238
+ async function createModuleSymlink() {
239
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules');
240
+ const sharedPath = sharedModules.getSharedModulesPath();
241
+
242
+ // Check if node_modules already exists
243
+ if (await fs.pathExists(nodeModulesPath)) {
244
+ const stat = await fs.lstat(nodeModulesPath);
245
+ if (stat.isSymbolicLink()) {
246
+ // Already a symlink, update it
247
+ await fs.remove(nodeModulesPath);
248
+ } else {
249
+ // It's a real directory, don't overwrite
250
+ logger.dim('node_modules directory exists, skipping symlink creation');
251
+ return;
252
+ }
253
+ }
254
+
255
+ // Create symlink
256
+ try {
257
+ await fs.symlink(sharedPath, nodeModulesPath, 'junction');
258
+ logger.dim('Created symlink: node_modules -> shared modules');
259
+ } catch (error) {
260
+ // Fallback: copy package.json references
261
+ logger.dim('Could not create symlink, using NODE_PATH instead');
262
+ }
263
+ }
264
+
265
+ function formatSize(bytes) {
266
+ if (bytes === 0) return '0 Bytes';
267
+ const k = 1024;
268
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
269
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
270
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
271
+ }
272
+
273
+ // Add sub-commands
274
+ modulesCommand
275
+ .command('install [name]')
276
+ .description('Install modules to shared folder')
277
+ .option('-f, --force', 'Force reinstall')
278
+ .action(async (name, options) => {
279
+ await modulesInstall({ name, force: options.force });
280
+ });
281
+
282
+ modulesCommand
283
+ .command('update [name]')
284
+ .description('Update modules')
285
+ .option('-f, --force', 'Force update')
286
+ .action(async (name, options) => {
287
+ await modulesUpdate({ name, force: options.force });
288
+ });
289
+
290
+ modulesCommand
291
+ .command('list')
292
+ .description('List installed modules')
293
+ .action(async () => {
294
+ await modulesList({});
295
+ });
296
+
297
+ modulesCommand
298
+ .command('clean')
299
+ .description('Remove unused modules')
300
+ .action(async () => {
301
+ await modulesClean({});
302
+ });
303
+
304
+ modulesCommand
305
+ .command('sync')
306
+ .description('Sync project dependencies to shared modules')
307
+ .option('-f, --force', 'Force sync')
308
+ .action(async (options) => {
309
+ await modulesSync({ force: options.force });
310
+ });
311
+
312
+ modulesCommand
313
+ .command('stats')
314
+ .description('Show disk usage statistics')
315
+ .action(async () => {
316
+ await modulesStats({});
317
+ });
318
+
319
+ modulesCommand
320
+ .command('link')
321
+ .description('Link local node_modules to shared folder')
322
+ .action(async () => {
323
+ await modulesLink({});
324
+ });
325
+
326
+ modulesCommand
327
+ .command('open')
328
+ .description('Open shared modules folder')
329
+ .action(async () => {
330
+ await modulesOpen({});
331
+ });
332
+
333
+ module.exports = modulesCommand;
@@ -0,0 +1,265 @@
1
+ const { Command } = require('commander');
2
+ const { spawn } = require('child_process');
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const logger = require('../utils/logger');
6
+ const ProjectDetector = require('../utils/detector');
7
+ const ConfigLoader = require('../utils/config');
8
+
9
+ const packageCommand = new Command('package')
10
+ .description('Package your application for distribution')
11
+ .option('-t, --type <type>', 'Package type (bin, app, docker, npm)', 'bin')
12
+ .option('-p, --platform <platform>', 'Target platform (win, linux, mac, current)', 'current')
13
+ .option('--arch <arch>', 'Target architecture (x64, arm64, all)', 'x64')
14
+ .option('-o, --output <dir>', 'Output directory', 'dist')
15
+ .action(async (options) => {
16
+ logger.header('📦 Packaging Application');
17
+
18
+ const detector = new ProjectDetector();
19
+ const config = new ConfigLoader();
20
+
21
+ try {
22
+ const projectInfo = await detector.detect();
23
+ const configData = await config.load();
24
+
25
+ logger.info(`Type: ${options.type}`);
26
+ logger.info(`Platform: ${options.platform}`);
27
+ logger.info(`Architecture: ${options.arch}`);
28
+
29
+ await packageApp(projectInfo, configData, options);
30
+ } catch (error) {
31
+ logger.error(`Packaging failed: ${error.message}`);
32
+ process.exit(1);
33
+ }
34
+ });
35
+
36
+ async function packageApp(projectInfo, config, options) {
37
+ const types = {
38
+ bin: packageAsBinary,
39
+ app: packageAsApp,
40
+ docker: packageAsDocker,
41
+ npm: packageAsNpm,
42
+ single: packageAsSingleFile,
43
+ };
44
+
45
+ const packager = types[options.type];
46
+ if (!packager) {
47
+ throw new Error(`Unknown package type: ${options.type}`);
48
+ }
49
+
50
+ await packager(projectInfo, config, options);
51
+ }
52
+
53
+ async function packageAsBinary(projectInfo, config, options) {
54
+ logger.info('Packaging as standalone binary...');
55
+
56
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
57
+ const entryPoint = pkg.main || 'src/index.js';
58
+
59
+ // Ensure pkg is available
60
+ try {
61
+ require.resolve('pkg');
62
+ } catch {
63
+ logger.info('Installing pkg...');
64
+ await new Promise((resolve, reject) => {
65
+ const install = spawn('npm', ['install', 'pkg', '--save-dev'], {
66
+ stdio: 'inherit',
67
+ shell: true,
68
+ });
69
+ install.on('close', (code) => {
70
+ if (code === 0) resolve();
71
+ else reject(new Error('Failed to install pkg'));
72
+ });
73
+ });
74
+ }
75
+
76
+ const platforms = {
77
+ win: 'win',
78
+ linux: 'linux',
79
+ mac: 'macos',
80
+ current: process.platform === 'win32' ? 'win' : process.platform === 'darwin' ? 'macos' : 'linux',
81
+ };
82
+
83
+ const target = platforms[options.platform] || 'linux';
84
+ const arch = options.arch === 'all' ? 'x64' : options.arch;
85
+
86
+ const args = [
87
+ 'pkg',
88
+ entryPoint,
89
+ '--targets',
90
+ `${target}-${arch}`,
91
+ '--output',
92
+ `${options.output}/${pkg.name}`,
93
+ ];
94
+
95
+ const child = spawn('npx', args, {
96
+ stdio: 'inherit',
97
+ shell: true,
98
+ });
99
+
100
+ return new Promise((resolve, reject) => {
101
+ child.on('close', (code) => {
102
+ if (code === 0) {
103
+ logger.success(`Binary packaged: ${options.output}/${pkg.name}`);
104
+ resolve();
105
+ } else {
106
+ reject(new Error('Binary packaging failed'));
107
+ }
108
+ });
109
+ });
110
+ }
111
+
112
+ async function packageAsApp(projectInfo, config, options) {
113
+ logger.info('Packaging as application...');
114
+
115
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
116
+
117
+ // Check if it's an Electron app
118
+ if (projectInfo.type === 'desktop' || pkg.dependencies?.electron || pkg.devDependencies?.electron) {
119
+ // Use electron-builder
120
+ const args = ['electron-builder'];
121
+
122
+ if (options.platform === 'all') {
123
+ args.push('-mwl');
124
+ } else if (options.platform === 'win') {
125
+ args.push('--win');
126
+ } else if (options.platform === 'mac') {
127
+ args.push('--mac');
128
+ } else if (options.platform === 'linux') {
129
+ args.push('--linux');
130
+ }
131
+
132
+ const child = spawn('npx', args, {
133
+ stdio: 'inherit',
134
+ shell: true,
135
+ });
136
+
137
+ return new Promise((resolve, reject) => {
138
+ child.on('close', (code) => {
139
+ if (code === 0) {
140
+ logger.success('Application packaged!');
141
+ resolve();
142
+ } else {
143
+ reject(new Error('Application packaging failed'));
144
+ }
145
+ });
146
+ });
147
+ }
148
+
149
+ // For Node.js apps, use pkg
150
+ await packageAsBinary(projectInfo, config, options);
151
+ }
152
+
153
+ async function packageAsDocker(projectInfo, config, options) {
154
+ logger.info('Packaging as Docker image...');
155
+
156
+ // Ensure Dockerfile exists
157
+ const dockerfilePath = path.join(process.cwd(), 'Dockerfile');
158
+ if (!(await fs.pathExists(dockerfilePath))) {
159
+ logger.warning('No Dockerfile found. Please run: easy docker build');
160
+ return;
161
+ }
162
+
163
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
164
+ const imageName = pkg.name || 'app';
165
+
166
+ const child = spawn('docker', ['build', '-t', `${imageName}:latest`, '.'], {
167
+ stdio: 'inherit',
168
+ shell: true,
169
+ });
170
+
171
+ return new Promise((resolve, reject) => {
172
+ child.on('close', (code) => {
173
+ if (code === 0) {
174
+ logger.success(`Docker image packaged: ${imageName}:latest`);
175
+ resolve();
176
+ } else {
177
+ reject(new Error('Docker packaging failed'));
178
+ }
179
+ });
180
+ });
181
+ }
182
+
183
+ async function packageAsNpm(projectInfo, config, options) {
184
+ logger.info('Packaging for npm...');
185
+
186
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
187
+
188
+ // Ensure package is ready for publishing
189
+ const requiredFields = ['name', 'version', 'main'];
190
+ const missing = requiredFields.filter(field => !pkg[field]);
191
+
192
+ if (missing.length > 0) {
193
+ logger.error(`Missing required fields in package.json: ${missing.join(', ')}`);
194
+ return;
195
+ }
196
+
197
+ // Build if needed
198
+ if (pkg.scripts?.build) {
199
+ logger.info('Building package...');
200
+ await new Promise((resolve, reject) => {
201
+ const build = spawn('npm', ['run', 'build'], {
202
+ stdio: 'inherit',
203
+ shell: true,
204
+ });
205
+ build.on('close', (code) => {
206
+ if (code === 0) resolve();
207
+ else reject(new Error('Build failed'));
208
+ });
209
+ });
210
+ }
211
+
212
+ // Pack
213
+ const child = spawn('npm', ['pack'], {
214
+ stdio: 'inherit',
215
+ shell: true,
216
+ });
217
+
218
+ return new Promise((resolve, reject) => {
219
+ child.on('close', (code) => {
220
+ if (code === 0) {
221
+ logger.success('Package created for npm!');
222
+ resolve();
223
+ } else {
224
+ reject(new Error('npm pack failed'));
225
+ }
226
+ });
227
+ });
228
+ }
229
+
230
+ async function packageAsSingleFile(projectInfo, config, options) {
231
+ logger.info('Packaging as single executable...');
232
+
233
+ // Use Node.js SEA (Single Executable Application) if available
234
+ logger.info('Note: Single Executable Application requires Node.js 20+');
235
+ logger.info('Building...');
236
+
237
+ const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
238
+ const entryPoint = pkg.main || 'src/index.js';
239
+
240
+ // Bundle with esbuild first
241
+ const child = spawn('npx', [
242
+ 'esbuild',
243
+ entryPoint,
244
+ '--bundle',
245
+ '--platform=node',
246
+ '--target=node20',
247
+ `--outfile=${options.output}/bundle.js`,
248
+ ], {
249
+ stdio: 'inherit',
250
+ shell: true,
251
+ });
252
+
253
+ return new Promise((resolve, reject) => {
254
+ child.on('close', (code) => {
255
+ if (code === 0) {
256
+ logger.success(`Single file packaged: ${options.output}/bundle.js`);
257
+ resolve();
258
+ } else {
259
+ reject(new Error('Single file packaging failed'));
260
+ }
261
+ });
262
+ });
263
+ }
264
+
265
+ module.exports = packageCommand;