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,73 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
const logger = require('../utils/logger');
|
|
6
|
+
|
|
7
|
+
const proxyCommand = new Command('proxy')
|
|
8
|
+
.description('Start a proxy server for development')
|
|
9
|
+
.option('-p, --port <port>', 'Port number', '8080')
|
|
10
|
+
.option('-t, --target <target>', 'Target URL to proxy to', 'http://localhost:3000')
|
|
11
|
+
.option('-c, --cors', 'Enable CORS', true)
|
|
12
|
+
.option('-l, --log', 'Log requests', true)
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
logger.header('🔄 Proxy Server');
|
|
15
|
+
logger.info(`Proxying to: ${options.target}`);
|
|
16
|
+
logger.info(`Listening on: http://localhost:${options.port}`);
|
|
17
|
+
logger.dim('Press Ctrl+C to stop');
|
|
18
|
+
logger.info('');
|
|
19
|
+
|
|
20
|
+
const server = http.createServer((req, res) => {
|
|
21
|
+
if (options.log) {
|
|
22
|
+
logger.dim(`${req.method} ${req.url}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Handle CORS
|
|
26
|
+
if (options.cors) {
|
|
27
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
28
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
29
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
30
|
+
|
|
31
|
+
if (req.method === 'OPTIONS') {
|
|
32
|
+
res.writeHead(200);
|
|
33
|
+
res.end();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Parse target URL
|
|
39
|
+
const targetUrl = new URL(req.url, options.target);
|
|
40
|
+
|
|
41
|
+
// Choose http or https based on protocol
|
|
42
|
+
const client = targetUrl.protocol === 'https:' ? https : http;
|
|
43
|
+
|
|
44
|
+
const proxyReq = client.request(
|
|
45
|
+
targetUrl,
|
|
46
|
+
{
|
|
47
|
+
method: req.method,
|
|
48
|
+
headers: {
|
|
49
|
+
...req.headers,
|
|
50
|
+
host: targetUrl.host,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
(proxyRes) => {
|
|
54
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
55
|
+
proxyRes.pipe(res);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
proxyReq.on('error', (error) => {
|
|
60
|
+
logger.error(`Proxy error: ${error.message}`);
|
|
61
|
+
res.writeHead(502);
|
|
62
|
+
res.end('Bad Gateway');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
req.pipe(proxyReq);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
server.listen(options.port, () => {
|
|
69
|
+
logger.success(`Proxy server running on http://localhost:${options.port}`);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
module.exports = proxyCommand;
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
const sharedModules = require('../utils/modules');
|
|
9
|
+
const globalConfig = require('../utils/globalConfig');
|
|
10
|
+
|
|
11
|
+
const runCommand = new Command('run')
|
|
12
|
+
.description('Run your project')
|
|
13
|
+
.option('-p, --port <port>', 'Port number', '3000')
|
|
14
|
+
.option('-h, --host <host>', 'Host address', 'localhost')
|
|
15
|
+
.option('-e, --env <environment>', 'Environment', 'development')
|
|
16
|
+
.option('--no-shared', 'Disable shared modules for this run')
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
logger.header('🏃 Running Project');
|
|
19
|
+
|
|
20
|
+
const detector = new ProjectDetector();
|
|
21
|
+
const config = new ConfigLoader();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const projectInfo = await detector.detect();
|
|
25
|
+
const configData = await config.load();
|
|
26
|
+
|
|
27
|
+
logger.info(`Framework: ${projectInfo.framework?.name || 'Unknown'}`);
|
|
28
|
+
logger.info(`Type: ${projectInfo.type || 'Unknown'}`);
|
|
29
|
+
logger.info(`Environment: ${options.env}`);
|
|
30
|
+
|
|
31
|
+
// Use shared modules by default (unless --no-shared is passed)
|
|
32
|
+
if (options.shared !== false) {
|
|
33
|
+
await sharedModules.init();
|
|
34
|
+
const configData = await globalConfig.load();
|
|
35
|
+
const useShared = configData.sharedModules?.enabled !== false;
|
|
36
|
+
|
|
37
|
+
if (useShared) {
|
|
38
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
39
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
40
|
+
logger.dim('Using shared modules...');
|
|
41
|
+
await sharedModules.installFromPackageJson(packageJsonPath);
|
|
42
|
+
await setupNodePath();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Set environment variables
|
|
48
|
+
process.env.NODE_ENV = options.env;
|
|
49
|
+
process.env.PORT = options.port;
|
|
50
|
+
process.env.HOST = options.host;
|
|
51
|
+
|
|
52
|
+
await runProject(projectInfo, configData, options);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.error(`Failed to run project: ${error.message}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
async function runProject(projectInfo, config, options) {
|
|
60
|
+
const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
|
|
61
|
+
const scripts = pkg.scripts || {};
|
|
62
|
+
|
|
63
|
+
// Determine the run command based on framework
|
|
64
|
+
let command = null;
|
|
65
|
+
let args = [];
|
|
66
|
+
|
|
67
|
+
if (projectInfo.framework) {
|
|
68
|
+
const runCommands = {
|
|
69
|
+
// Backend
|
|
70
|
+
express: 'node',
|
|
71
|
+
fastify: 'node',
|
|
72
|
+
koa: 'node',
|
|
73
|
+
hapi: 'node',
|
|
74
|
+
nestjs: { command: 'npm', args: ['run', 'start:dev'] },
|
|
75
|
+
adonis: { command: 'npm', args: ['run', 'dev'] },
|
|
76
|
+
strapi: { command: 'npm', args: ['run', 'develop'] },
|
|
77
|
+
hono: 'npm run dev',
|
|
78
|
+
trpc: 'npm run dev',
|
|
79
|
+
graphql: 'npm run dev',
|
|
80
|
+
socketio: 'npm run dev',
|
|
81
|
+
|
|
82
|
+
// Frontend
|
|
83
|
+
react: 'npm start',
|
|
84
|
+
vue: 'npm run serve',
|
|
85
|
+
angular: 'ng serve',
|
|
86
|
+
svelte: 'npm run dev',
|
|
87
|
+
solid: 'npm run dev',
|
|
88
|
+
preact: 'npm run dev',
|
|
89
|
+
lit: 'npm run dev',
|
|
90
|
+
alpine: 'npm run dev',
|
|
91
|
+
htmx: 'npm run dev',
|
|
92
|
+
|
|
93
|
+
// Full-stack
|
|
94
|
+
nextjs: 'npm run dev',
|
|
95
|
+
nuxt: 'npm run dev',
|
|
96
|
+
remix: 'npm run dev',
|
|
97
|
+
gatsby: 'npm run develop',
|
|
98
|
+
astro: 'npm run dev',
|
|
99
|
+
sveltekit: 'npm run dev',
|
|
100
|
+
solidstart: 'npm run dev',
|
|
101
|
+
qwik: 'npm run dev',
|
|
102
|
+
analog: 'npm run dev',
|
|
103
|
+
fresh: 'deno task dev',
|
|
104
|
+
tanstack: 'npm run dev',
|
|
105
|
+
waku: 'npm run dev',
|
|
106
|
+
|
|
107
|
+
// Desktop
|
|
108
|
+
electron: 'npm run dev',
|
|
109
|
+
tauri: 'npm run dev',
|
|
110
|
+
nwjs: 'npm run dev',
|
|
111
|
+
|
|
112
|
+
// Mobile
|
|
113
|
+
reactnative: 'npm start',
|
|
114
|
+
expo: 'npm start',
|
|
115
|
+
ionic: 'npm start',
|
|
116
|
+
tamagui: 'npm start',
|
|
117
|
+
nativebase: 'npm start',
|
|
118
|
+
|
|
119
|
+
// Database
|
|
120
|
+
drizzle: 'npm run dev',
|
|
121
|
+
prisma: 'npm run dev',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const runConfig = runCommands[projectInfo.framework.name];
|
|
125
|
+
|
|
126
|
+
if (typeof runConfig === 'object') {
|
|
127
|
+
command = runConfig.command;
|
|
128
|
+
args = runConfig.args;
|
|
129
|
+
} else if (runConfig) {
|
|
130
|
+
const parts = runConfig.split(' ');
|
|
131
|
+
command = parts[0];
|
|
132
|
+
args = parts.slice(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fallback to package.json scripts
|
|
137
|
+
if (!command) {
|
|
138
|
+
if (scripts.dev) {
|
|
139
|
+
command = 'npm';
|
|
140
|
+
args = ['run', 'dev'];
|
|
141
|
+
} else if (scripts.start) {
|
|
142
|
+
command = 'npm';
|
|
143
|
+
args = ['run', 'start'];
|
|
144
|
+
} else if (scripts.serve) {
|
|
145
|
+
command = 'npm';
|
|
146
|
+
args = ['run', 'serve'];
|
|
147
|
+
} else {
|
|
148
|
+
// Try to run the entry point directly
|
|
149
|
+
const entryPoint = config.entry || 'src/index.js';
|
|
150
|
+
if (await fs.pathExists(path.join(process.cwd(), entryPoint))) {
|
|
151
|
+
command = 'node';
|
|
152
|
+
args = [entryPoint];
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error('No run command found. Add a "dev" or "start" script to package.json');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
logger.info(`Running: ${command} ${args.join(' ')}`);
|
|
160
|
+
|
|
161
|
+
const child = spawn(command, args, {
|
|
162
|
+
stdio: 'inherit',
|
|
163
|
+
shell: true,
|
|
164
|
+
env: {
|
|
165
|
+
...process.env,
|
|
166
|
+
NODE_ENV: options.env,
|
|
167
|
+
PORT: options.port,
|
|
168
|
+
HOST: options.host,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
child.on('error', (error) => {
|
|
173
|
+
logger.error(`Process failed: ${error.message}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
child.on('close', (code) => {
|
|
178
|
+
if (code !== 0) {
|
|
179
|
+
logger.error(`Process exited with code ${code}`);
|
|
180
|
+
process.exit(code);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function setupNodePath() {
|
|
186
|
+
const sharedPath = sharedModules.getSharedModulesPath();
|
|
187
|
+
const currentPath = process.env.NODE_PATH || '';
|
|
188
|
+
|
|
189
|
+
if (!currentPath.includes(sharedPath)) {
|
|
190
|
+
process.env.NODE_PATH = currentPath
|
|
191
|
+
? `${sharedPath}${path.delimiter}${currentPath}`
|
|
192
|
+
: sharedPath;
|
|
193
|
+
|
|
194
|
+
// Rehash require paths
|
|
195
|
+
require('module')._initPaths();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = runCommand;
|
|
@@ -0,0 +1,104 @@
|
|
|
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 testCommand = new Command('test')
|
|
9
|
+
.description('Run tests')
|
|
10
|
+
.option('-w, --watch', 'Watch mode', false)
|
|
11
|
+
.option('-c, --coverage', 'Generate coverage report', false)
|
|
12
|
+
.option('-f, --filter <pattern>', 'Filter tests by pattern')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
logger.header('🧪 Running Tests');
|
|
15
|
+
|
|
16
|
+
const detector = new ProjectDetector();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const projectInfo = await detector.detect();
|
|
20
|
+
|
|
21
|
+
logger.info(`Framework: ${projectInfo.framework?.name || 'Unknown'}`);
|
|
22
|
+
|
|
23
|
+
await runTests(projectInfo, options);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.error(`Failed to run tests: ${error.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
async function runTests(projectInfo, options) {
|
|
31
|
+
const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
|
|
32
|
+
const scripts = pkg.scripts || {};
|
|
33
|
+
const deps = {
|
|
34
|
+
...pkg.dependencies,
|
|
35
|
+
...pkg.devDependencies,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let command = null;
|
|
39
|
+
let args = [];
|
|
40
|
+
|
|
41
|
+
// Detect test framework
|
|
42
|
+
if (deps.jest || scripts.test?.includes('jest')) {
|
|
43
|
+
command = 'npm';
|
|
44
|
+
args = ['test'];
|
|
45
|
+
if (options.watch) args.push('--watch');
|
|
46
|
+
if (options.coverage) args.push('--coverage');
|
|
47
|
+
if (options.filter) args.push('--testNamePattern', options.filter);
|
|
48
|
+
} else if (deps.vitest || scripts.test?.includes('vitest')) {
|
|
49
|
+
command = 'npm';
|
|
50
|
+
args = ['test'];
|
|
51
|
+
if (options.watch) args.push('--watch');
|
|
52
|
+
if (options.coverage) args.push('--coverage');
|
|
53
|
+
if (options.filter) args.push('-t', options.filter);
|
|
54
|
+
} else if (deps.mocha || scripts.test?.includes('mocha')) {
|
|
55
|
+
command = 'npm';
|
|
56
|
+
args = ['test'];
|
|
57
|
+
if (options.watch) args.push('--watch');
|
|
58
|
+
if (options.filter) args.push('--grep', options.filter);
|
|
59
|
+
} else if (deps['@playwright/test'] || scripts.test?.includes('playwright')) {
|
|
60
|
+
command = 'npx';
|
|
61
|
+
args = ['playwright', 'test'];
|
|
62
|
+
if (options.filter) args.push(options.filter);
|
|
63
|
+
} else if (deps.cypress || scripts.test?.includes('cypress')) {
|
|
64
|
+
command = 'npx';
|
|
65
|
+
args = ['cypress', 'run'];
|
|
66
|
+
if (options.filter) args.push('--spec', options.filter);
|
|
67
|
+
} else if (scripts.test) {
|
|
68
|
+
command = 'npm';
|
|
69
|
+
args = ['test'];
|
|
70
|
+
} else {
|
|
71
|
+
// Try to run node test files
|
|
72
|
+
command = 'node';
|
|
73
|
+
args = ['--test'];
|
|
74
|
+
if (options.watch) args.push('--watch');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
logger.info(`Running: ${command} ${args.join(' ')}`);
|
|
78
|
+
logger.info('');
|
|
79
|
+
|
|
80
|
+
const child = spawn(command, args, {
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
shell: true,
|
|
83
|
+
env: {
|
|
84
|
+
...process.env,
|
|
85
|
+
NODE_ENV: 'test',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
child.on('error', (error) => {
|
|
90
|
+
logger.error(`Test process failed: ${error.message}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
child.on('close', (code) => {
|
|
95
|
+
if (code !== 0) {
|
|
96
|
+
logger.error(`Tests failed with code ${code}`);
|
|
97
|
+
process.exit(code);
|
|
98
|
+
} else {
|
|
99
|
+
logger.success('All tests passed!');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = testCommand;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const ProjectDetector = require('./utils/detector');
|
|
2
|
+
const ConfigLoader = require('./utils/config');
|
|
3
|
+
const logger = require('./utils/logger');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
ProjectDetector,
|
|
7
|
+
ConfigLoader,
|
|
8
|
+
logger,
|
|
9
|
+
|
|
10
|
+
// Commands
|
|
11
|
+
run: require('./commands/run'),
|
|
12
|
+
build: require('./commands/build'),
|
|
13
|
+
init: require('./commands/init'),
|
|
14
|
+
dev: require('./commands/dev'),
|
|
15
|
+
test: require('./commands/test'),
|
|
16
|
+
deploy: require('./commands/deploy'),
|
|
17
|
+
docker: require('./commands/docker'),
|
|
18
|
+
electron: require('./commands/electron'),
|
|
19
|
+
package: require('./commands/package'),
|
|
20
|
+
analyze: require('./commands/analyze'),
|
|
21
|
+
clean: require('./commands/clean'),
|
|
22
|
+
lint: require('./commands/lint'),
|
|
23
|
+
|
|
24
|
+
// Targets
|
|
25
|
+
NodeBuilder: require('./targets/node'),
|
|
26
|
+
FrontendBuilder: require('./targets/frontend'),
|
|
27
|
+
ElectronBuilder: require('./targets/electron'),
|
|
28
|
+
DockerBuilder: require('./targets/docker'),
|
|
29
|
+
LibraryBuilder: require('./targets/library'),
|
|
30
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const logger = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
class DockerBuilder {
|
|
7
|
+
constructor(projectInfo, config) {
|
|
8
|
+
this.projectInfo = projectInfo;
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.dockerConfig = config.docker || {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async build(options) {
|
|
14
|
+
logger.section('Building Docker Image');
|
|
15
|
+
|
|
16
|
+
// Ensure Dockerfile exists
|
|
17
|
+
await this.ensureDockerfile();
|
|
18
|
+
|
|
19
|
+
// Build image
|
|
20
|
+
await this.buildImage(options);
|
|
21
|
+
|
|
22
|
+
// Optionally run
|
|
23
|
+
if (options.run) {
|
|
24
|
+
await this.runContainer(options);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async ensureDockerfile() {
|
|
29
|
+
const dockerfilePath = path.join(process.cwd(), 'Dockerfile');
|
|
30
|
+
|
|
31
|
+
if (!(await fs.pathExists(dockerfilePath))) {
|
|
32
|
+
logger.info('Generating Dockerfile...');
|
|
33
|
+
|
|
34
|
+
const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
|
|
35
|
+
const baseImage = this.dockerConfig.baseImage || 'node:20-alpine';
|
|
36
|
+
const workdir = this.dockerConfig.workdir || '/app';
|
|
37
|
+
const port = this.dockerConfig.port || 3000;
|
|
38
|
+
|
|
39
|
+
// Determine if it's a frontend or backend project
|
|
40
|
+
const isFrontend = this.projectInfo.type === 'frontend' || this.projectInfo.type === 'fullstack';
|
|
41
|
+
|
|
42
|
+
let dockerfile;
|
|
43
|
+
|
|
44
|
+
if (isFrontend) {
|
|
45
|
+
dockerfile = `# Build stage
|
|
46
|
+
FROM ${baseImage} AS builder
|
|
47
|
+
|
|
48
|
+
WORKDIR ${workdir}
|
|
49
|
+
|
|
50
|
+
COPY package*.json ./
|
|
51
|
+
RUN npm ci
|
|
52
|
+
|
|
53
|
+
COPY . .
|
|
54
|
+
RUN npm run build
|
|
55
|
+
|
|
56
|
+
# Production stage
|
|
57
|
+
FROM nginx:alpine
|
|
58
|
+
|
|
59
|
+
COPY --from=builder ${workdir}/dist /usr/share/nginx/html
|
|
60
|
+
|
|
61
|
+
EXPOSE 80
|
|
62
|
+
|
|
63
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
64
|
+
`;
|
|
65
|
+
} else {
|
|
66
|
+
dockerfile = `# Build stage
|
|
67
|
+
FROM ${baseImage} AS builder
|
|
68
|
+
|
|
69
|
+
WORKDIR ${workdir}
|
|
70
|
+
|
|
71
|
+
COPY package*.json ./
|
|
72
|
+
RUN npm ci
|
|
73
|
+
|
|
74
|
+
COPY . .
|
|
75
|
+
|
|
76
|
+
# Build if build script exists
|
|
77
|
+
RUN npm run build 2>/dev/null || true
|
|
78
|
+
|
|
79
|
+
# Production stage
|
|
80
|
+
FROM ${baseImage}
|
|
81
|
+
|
|
82
|
+
WORKDIR ${workdir}
|
|
83
|
+
|
|
84
|
+
COPY package*.json ./
|
|
85
|
+
RUN npm ci --only=production
|
|
86
|
+
|
|
87
|
+
COPY --from=builder ${workdir}/dist ./dist
|
|
88
|
+
COPY --from=builder ${workdir}/src ./src
|
|
89
|
+
|
|
90
|
+
EXPOSE ${port}
|
|
91
|
+
|
|
92
|
+
CMD ["node", "src/index.js"]
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await fs.writeFile(dockerfilePath, dockerfile);
|
|
97
|
+
logger.success('Dockerfile generated');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Ensure .dockerignore exists
|
|
101
|
+
const dockerignorePath = path.join(process.cwd(), '.dockerignore');
|
|
102
|
+
if (!(await fs.pathExists(dockerignorePath))) {
|
|
103
|
+
const dockerignore = `node_modules
|
|
104
|
+
npm-debug.log
|
|
105
|
+
dist
|
|
106
|
+
build
|
|
107
|
+
.git
|
|
108
|
+
.env
|
|
109
|
+
.env.*
|
|
110
|
+
*.log
|
|
111
|
+
`;
|
|
112
|
+
await fs.writeFile(dockerignorePath, dockerignore);
|
|
113
|
+
logger.success('.dockerignore generated');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async buildImage(options) {
|
|
118
|
+
const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
|
|
119
|
+
const imageName = pkg.name || 'app';
|
|
120
|
+
const tag = options.tag || 'latest';
|
|
121
|
+
|
|
122
|
+
logger.info(`Building Docker image: ${imageName}:${tag}`);
|
|
123
|
+
|
|
124
|
+
const args = ['build', '-t', `${imageName}:${tag}`, '.'];
|
|
125
|
+
|
|
126
|
+
const child = spawn('docker', args, {
|
|
127
|
+
stdio: 'inherit',
|
|
128
|
+
shell: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
child.on('close', (code) => {
|
|
133
|
+
if (code === 0) {
|
|
134
|
+
logger.success(`Docker image built: ${imageName}:${tag}`);
|
|
135
|
+
resolve();
|
|
136
|
+
} else {
|
|
137
|
+
reject(new Error('Docker build failed'));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async runContainer(options) {
|
|
144
|
+
const pkg = await fs.readJson(path.join(process.cwd(), 'package.json'));
|
|
145
|
+
const imageName = pkg.name || 'app';
|
|
146
|
+
const tag = options.tag || 'latest';
|
|
147
|
+
const containerName = `${imageName}-container`;
|
|
148
|
+
const port = this.dockerConfig.port || 3000;
|
|
149
|
+
|
|
150
|
+
logger.info(`Running container: ${containerName}`);
|
|
151
|
+
|
|
152
|
+
// Stop existing container if running
|
|
153
|
+
try {
|
|
154
|
+
await new Promise((resolve) => {
|
|
155
|
+
const stop = spawn('docker', ['stop', containerName], {
|
|
156
|
+
shell: true,
|
|
157
|
+
});
|
|
158
|
+
stop.on('close', () => resolve());
|
|
159
|
+
});
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Ignore
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Remove existing container
|
|
165
|
+
try {
|
|
166
|
+
await new Promise((resolve) => {
|
|
167
|
+
const rm = spawn('docker', ['rm', containerName], {
|
|
168
|
+
shell: true,
|
|
169
|
+
});
|
|
170
|
+
rm.on('close', () => resolve());
|
|
171
|
+
});
|
|
172
|
+
} catch (e) {
|
|
173
|
+
// Ignore
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const args = [
|
|
177
|
+
'run', '-d',
|
|
178
|
+
'--name', containerName,
|
|
179
|
+
'-p', `${port}:${port}`,
|
|
180
|
+
`${imageName}:${tag}`,
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const child = spawn('docker', args, {
|
|
184
|
+
stdio: 'inherit',
|
|
185
|
+
shell: true,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
child.on('close', (code) => {
|
|
190
|
+
if (code === 0) {
|
|
191
|
+
logger.success(`Container running on port ${port}`);
|
|
192
|
+
resolve();
|
|
193
|
+
} else {
|
|
194
|
+
reject(new Error('Docker run failed'));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = DockerBuilder;
|