ewvjs-cli 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miukyo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # ewvjs-cli
2
+
3
+ CLI tool for packaging ewvjs applications into standalone executables.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g ewvjs-cli
9
+ ```
10
+
11
+ Or use with npx:
12
+
13
+ ```bash
14
+ npx ewvjs-cli ewv --help
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### List available templates
20
+
21
+ ```bash
22
+ ewv init --list-templates
23
+ ```
24
+
25
+ ### Initialize a new ewvjs project
26
+
27
+ ```bash
28
+ ewv init my-app
29
+ ```
30
+
31
+ With a specific template:
32
+
33
+ ```bash
34
+ # Minimal template (simple Hello World)
35
+ ewv init my-app --template minimal
36
+
37
+ # Basic template (default - interactive with APIs)
38
+ ewv init my-app --template basic
39
+
40
+ # Advanced template (multiple windows, file operations)
41
+ ewv init my-app --template advanced
42
+ ```
43
+
44
+ ### Package your application
45
+
46
+ ```bash
47
+ ewv package app.js -o myapp -n "My Application"
48
+ ```
49
+
50
+ #### Options
51
+
52
+ - `-o, --output <name>` - Output executable name (default: "app")
53
+ - `-a, --assets <dir>` - Assets directory to include (default: "./assets")
54
+ - `-i, --icon <file>` - Application icon (.ico file)
55
+ - `-n, --name <name>` - Application name (default: "My App")
56
+ - `-t, --target <target>` - Target platform (default: "node18-win-x64")
57
+ - `-m, --modules <modules>` - Additional node modules to bundle (comma-separated)
58
+ - `--compress` - Compress the executable with UPX
59
+ - `--no-native` - Skip bundling native DLLs
60
+
61
+ ### Example
62
+
63
+ ```bash
64
+ # Create a new project
65
+ ewv init my-awesome-app
66
+ cd my-awesome-app
67
+
68
+ # Install dependencies
69
+ npm install
70
+
71
+ # Run in development
72
+ npm start
73
+
74
+ # Create with a specific template
75
+ ewv init simple-app --template minimal
76
+
77
+ # Create an advanced app
78
+ ewv init pro-app --template advanced
79
+
80
+ # Package for distribution
81
+ ewv package app.js -o MyAwesomeApp -n "My Awesome App" -i icon.ico --compress
82
+ ```
83
+
84
+ ## Templates
85
+
86
+ ewvjs-cli includes three project templates:
87
+
88
+ ### minimal
89
+ A simple "Hello World" template - perfect for quick testing.
90
+
91
+ ### basic (default)
92
+ Interactive application with exposed Node.js functions and a polished UI.
93
+
94
+ ### advanced
95
+ Comprehensive template with multiple windows, file operations, and external assets.
96
+
97
+ Use `ewv init --list-templates` to see all available templates.
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const { input, select } = require('@inquirer/prompts');
7
+ const packageApp = require('../lib/packager');
8
+ const { setIcon } = require('../lib/icon');
9
+ const { getAvailableTemplates, copyTemplate, isValidTemplate } = require('../lib/templates');
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('ewv')
15
+ .description('CLI tool for packaging ewvjs applications')
16
+ .version(require('../package.json').version);
17
+
18
+ program
19
+ .command('package')
20
+ .description('Package your ewvjs application into a standalone executable')
21
+ .argument('<entry>', 'Entry point JavaScript file (e.g., app.js)')
22
+ .option('-o, --output <name>', 'Output executable name', 'app')
23
+ .option('-a, --assets <dir>', 'Assets directory to include (e.g., ./assets)', './assets')
24
+ .option('-i, --icon <file>', 'Application icon (.ico file)')
25
+ .option('-n, --name <name>', 'Application name', 'My App')
26
+ .option('-t, --target <target>', 'Target platform', 'node18-win-x64')
27
+ .option('-m, --modules <modules>', 'Additional node modules to bundle (comma-separated, e.g., "axios,lodash")')
28
+ .option('--compress', 'Compress the executable with UPX', false)
29
+ .option('--no-native', 'Skip bundling native DLLs (use if already included)')
30
+ .action(async (entry, options) => {
31
+ try {
32
+ console.log('📦 Packaging ewvjs application...\n');
33
+
34
+ // Validate entry file exists
35
+ const entryPath = path.resolve(process.cwd(), entry);
36
+ if (!fs.existsSync(entryPath)) {
37
+ console.error(`❌ Error: Entry file not found: ${entry}`);
38
+ process.exit(1);
39
+ }
40
+
41
+ // Validate icon if provided
42
+ if (options.icon) {
43
+ const iconPath = path.resolve(process.cwd(), options.icon);
44
+ if (!fs.existsSync(iconPath)) {
45
+ console.error(`❌ Error: Icon file not found: ${options.icon}`);
46
+ process.exit(1);
47
+ }
48
+ if (!iconPath.endsWith('.ico')) {
49
+ console.error('❌ Error: Icon must be a .ico file');
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ // Parse additional modules if provided
55
+ const additionalModules = options.modules
56
+ ? options.modules.split(',').map(m => m.trim()).filter(m => m)
57
+ : [];
58
+
59
+ const config = {
60
+ entry: entryPath,
61
+ output: options.output,
62
+ assets: options.assets ? path.resolve(process.cwd(), options.assets) : null,
63
+ icon: options.icon ? path.resolve(process.cwd(), options.icon) : null,
64
+ name: options.name,
65
+ target: options.target,
66
+ compress: options.compress,
67
+ includeNative: options.native,
68
+ additionalModules: additionalModules
69
+ };
70
+
71
+ await packageApp(config);
72
+
73
+ console.log('\n✅ Packaging complete!');
74
+ console.log(`📁 Output folder: ${path.join(process.cwd(), 'dist')}`);
75
+ console.log(`📦 Archive: ${path.join(process.cwd(), options.output + '.zip')}`);
76
+
77
+ } catch (error) {
78
+ console.error('\n❌ Packaging failed:', error.message);
79
+ process.exit(1);
80
+ }
81
+ });
82
+
83
+ program
84
+ .command('init')
85
+ .description('Initialize a new ewvjs project')
86
+ .argument('[name]', 'Project name')
87
+ .option('-t, --template <template>', 'Template to use (minimal, basic, advanced)')
88
+ .option('-l, --list-templates', 'List available templates')
89
+ .action(async (name, options) => {
90
+ // Handle list templates option
91
+ if (options.listTemplates) {
92
+ console.log('📋 Available templates:\n');
93
+ const templates = getAvailableTemplates();
94
+ templates.forEach(t => {
95
+ const isDefault = t.name === 'basic' ? ' (default)' : '';
96
+ console.log(` ${t.name}${isDefault}`);
97
+ console.log(` ${t.description}\n`);
98
+ });
99
+ console.log('Usage: ewv init <project-name> --template <template-name>');
100
+ return;
101
+ }
102
+
103
+ const templates = getAvailableTemplates();
104
+ const answers = {};
105
+
106
+ // Prompt for project name if not provided
107
+ if (!name) {
108
+ answers.projectName = await input({
109
+ message: 'Project name:',
110
+ default: 'my-ewvjs-app',
111
+ validate: (value) => {
112
+ if (!value || value.trim() === '') {
113
+ return 'Project name is required';
114
+ }
115
+ if (fs.existsSync(path.join(process.cwd(), value))) {
116
+ return `Directory ${value} already exists`;
117
+ }
118
+ return true;
119
+ }
120
+ });
121
+ } else {
122
+ answers.projectName = name;
123
+ }
124
+
125
+ // Prompt for template if not provided
126
+ if (!options.template) {
127
+ answers.template = await select({
128
+ message: 'Select a template:',
129
+ choices: templates.map(t => ({
130
+ name: `${t.name} - ${t.description}`,
131
+ value: t.name
132
+ })),
133
+ default: 'basic'
134
+ });
135
+ } else {
136
+ answers.template = options.template;
137
+ }
138
+
139
+ const projectDir = path.join(process.cwd(), answers.projectName);
140
+
141
+ // Validate template
142
+ if (!isValidTemplate(answers.template)) {
143
+ console.error(`❌ Error: Template "${answers.template}" not found`);
144
+ console.log('\nAvailable templates:');
145
+ templates.forEach(t => {
146
+ console.log(` - ${t.name}: ${t.description}`);
147
+ });
148
+ process.exit(1);
149
+ }
150
+
151
+ if (fs.existsSync(projectDir)) {
152
+ console.error(`❌ Error: Directory ${answers.projectName} already exists`);
153
+ process.exit(1);
154
+ }
155
+
156
+ console.log(`\n📂 Creating new ewvjs project: ${answers.projectName}`);
157
+ console.log(`📝 Using template: ${answers.template}\n`);
158
+
159
+ try {
160
+ // Copy template to project directory
161
+ copyTemplate(answers.template, answers.projectName, projectDir);
162
+
163
+ console.log('✅ Project created successfully!\n');
164
+ console.log('Next steps:');
165
+ console.log(` cd ${answers.projectName}`);
166
+ console.log(' npm install');
167
+ console.log(' npm start\n');
168
+ } catch (error) {
169
+ console.error(`❌ Error creating project: ${error.message}`);
170
+ process.exit(1);
171
+ }
172
+ });
173
+
174
+ program.parse();
package/lib/assets.js ADDED
@@ -0,0 +1,129 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const archiver = require('archiver');
4
+
5
+ /**
6
+ * Bundle assets directory
7
+ * @param {string} assetsDir - Source assets directory
8
+ * @param {string} outputDir - Destination directory
9
+ * @returns {Promise<void>}
10
+ */
11
+ async function bundleAssets(assetsDir, outputDir) {
12
+ if (!fs.existsSync(assetsDir)) {
13
+ throw new Error(`Assets directory not found: ${assetsDir}`);
14
+ }
15
+
16
+ // Create output directory
17
+ if (!fs.existsSync(outputDir)) {
18
+ fs.mkdirSync(outputDir, { recursive: true });
19
+ }
20
+
21
+ // Copy assets directory
22
+ await copyDirectory(assetsDir, outputDir);
23
+
24
+ // Get stats
25
+ const stats = getDirectoryStats(outputDir);
26
+ console.log(` Copied ${stats.files} files (${formatBytes(stats.size)})`);
27
+ }
28
+
29
+ /**
30
+ * Copy directory recursively
31
+ * @param {string} src - Source directory
32
+ * @param {string} dest - Destination directory
33
+ */
34
+ async function copyDirectory(src, dest) {
35
+ if (!fs.existsSync(dest)) {
36
+ fs.mkdirSync(dest, { recursive: true });
37
+ }
38
+
39
+ const entries = fs.readdirSync(src, { withFileTypes: true });
40
+
41
+ for (const entry of entries) {
42
+ const srcPath = path.join(src, entry.name);
43
+ const destPath = path.join(dest, entry.name);
44
+
45
+ if (entry.isDirectory()) {
46
+ await copyDirectory(srcPath, destPath);
47
+ } else {
48
+ fs.copyFileSync(srcPath, destPath);
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Create a zip archive of assets (alternative to direct copy)
55
+ * @param {string} assetsDir - Source assets directory
56
+ * @param {string} outputPath - Output zip file path
57
+ * @returns {Promise<void>}
58
+ */
59
+ async function createAssetsArchive(assetsDir, outputPath) {
60
+ return new Promise((resolve, reject) => {
61
+ const output = fs.createWriteStream(outputPath);
62
+ const archive = archiver('zip', {
63
+ zlib: { level: 9 } // Maximum compression
64
+ });
65
+
66
+ output.on('close', () => {
67
+ console.log(` Created archive: ${formatBytes(archive.pointer())}`);
68
+ resolve();
69
+ });
70
+
71
+ archive.on('error', (err) => {
72
+ reject(err);
73
+ });
74
+
75
+ archive.pipe(output);
76
+ archive.directory(assetsDir, false);
77
+ archive.finalize();
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Get directory statistics
83
+ * @param {string} dirPath - Directory path
84
+ * @returns {{files: number, size: number}}
85
+ */
86
+ function getDirectoryStats(dirPath) {
87
+ let fileCount = 0;
88
+ let totalSize = 0;
89
+
90
+ function traverse(dir) {
91
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
92
+
93
+ for (const entry of entries) {
94
+ const fullPath = path.join(dir, entry.name);
95
+
96
+ if (entry.isDirectory()) {
97
+ traverse(fullPath);
98
+ } else {
99
+ fileCount++;
100
+ totalSize += fs.statSync(fullPath).size;
101
+ }
102
+ }
103
+ }
104
+
105
+ traverse(dirPath);
106
+ return { files: fileCount, size: totalSize };
107
+ }
108
+
109
+ /**
110
+ * Format bytes to human-readable string
111
+ * @param {number} bytes - Number of bytes
112
+ * @returns {string}
113
+ */
114
+ function formatBytes(bytes) {
115
+ if (bytes === 0) return '0 Bytes';
116
+
117
+ const k = 1024;
118
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
119
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
120
+
121
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
122
+ }
123
+
124
+ module.exports = {
125
+ bundleAssets,
126
+ createAssetsArchive,
127
+ copyDirectory,
128
+ formatBytes
129
+ };
package/lib/icon.js ADDED
@@ -0,0 +1,150 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const NtExecutable = require('resedit').NtExecutable;
4
+ const NtExecutableResource = require('resedit').NtExecutableResource;
5
+ const Resource = require('resedit').Resource;
6
+
7
+ /**
8
+ * Set icon and metadata for Windows executable
9
+ * @param {string} exePath - Path to the executable
10
+ * @param {string} iconPath - Path to the .ico file
11
+ * @param {string} appName - Application name
12
+ * @param {Object} options - Additional metadata options
13
+ * @returns {Promise<void>}
14
+ */
15
+ async function setIcon(exePath, iconPath, appName, options = {}) {
16
+ try {
17
+ // Read the executable
18
+ const exeBuffer = fs.readFileSync(exePath);
19
+ const exe = NtExecutable.from(exeBuffer);
20
+ const res = NtExecutableResource.from(exe);
21
+
22
+ // Read the icon file
23
+ if (iconPath && fs.existsSync(iconPath)) {
24
+ const iconBuffer = fs.readFileSync(iconPath);
25
+
26
+ // Parse icon data
27
+ const iconFile = Resource.IconFile.from(iconBuffer);
28
+
29
+ // Replace icon in executable
30
+ Resource.IconGroupEntry.replaceIconsForResource(
31
+ res.entries,
32
+ 1, // Icon group ID
33
+ 1033, // Language (English - United States)
34
+ iconFile.icons.map((icon) => icon.data)
35
+ );
36
+
37
+ console.log(` Applied icon: ${path.basename(iconPath)}`);
38
+ }
39
+
40
+ // Set version info
41
+ const viList = Resource.VersionInfo.fromEntries(res.entries);
42
+ const vi = viList[0] || Resource.VersionInfo.createEmpty();
43
+
44
+ const {
45
+ version = '1.0.0.0',
46
+ companyName = '',
47
+ fileDescription = appName,
48
+ copyright = `Copyright © ${new Date().getFullYear()}`,
49
+ productName = appName,
50
+ internalName = appName.replace(/\s+/g, ''),
51
+ } = options;
52
+
53
+ // Parse version string
54
+ const [major = 1, minor = 0, patch = 0, build = 0] = version.split('.').map(Number);
55
+
56
+ // Set version numbers
57
+ vi.setFileVersion(major, minor, patch, build, 1033);
58
+ vi.setProductVersion(major, minor, patch, build, 1033);
59
+
60
+ // Set string values
61
+ vi.setStringValues(
62
+ { lang: 1033, codepage: 1200 },
63
+ {
64
+ ProductName: productName,
65
+ FileDescription: fileDescription,
66
+ CompanyName: companyName,
67
+ LegalCopyright: copyright,
68
+ FileVersion: version,
69
+ ProductVersion: version,
70
+ InternalName: internalName,
71
+ OriginalFilename: path.basename(exePath)
72
+ }
73
+ );
74
+
75
+ vi.outputToResourceEntries(res.entries);
76
+
77
+ // Write back to executable
78
+ res.outputResource(exe);
79
+ const newBuffer = exe.generate();
80
+ fs.writeFileSync(exePath, Buffer.from(newBuffer));
81
+
82
+ console.log(` Applied metadata: ${appName} v${version}`);
83
+ } catch (error) {
84
+ console.warn(` ⚠ Warning: Could not set icon/metadata: ${error.message}`);
85
+ console.warn(' The executable was created but icon/metadata may be missing.');
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Validate icon file
91
+ * @param {string} iconPath - Path to icon file
92
+ * @returns {boolean}
93
+ */
94
+ function validateIcon(iconPath) {
95
+ if (!fs.existsSync(iconPath)) {
96
+ throw new Error(`Icon file not found: ${iconPath}`);
97
+ }
98
+
99
+ if (!iconPath.toLowerCase().endsWith('.ico')) {
100
+ throw new Error('Icon file must be in .ico format');
101
+ }
102
+
103
+ const stats = fs.statSync(iconPath);
104
+ if (stats.size === 0) {
105
+ throw new Error('Icon file is empty');
106
+ }
107
+
108
+ if (stats.size > 1024 * 1024) {
109
+ console.warn('Warning: Icon file is larger than 1MB, consider optimizing it');
110
+ }
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Extract icon from executable
117
+ * @param {string} exePath - Path to executable
118
+ * @param {string} outputPath - Output path for icon file
119
+ * @returns {Promise<void>}
120
+ */
121
+ async function extractIcon(exePath, outputPath) {
122
+ try {
123
+ const exeBuffer = fs.readFileSync(exePath);
124
+ const exe = NtExecutable.from(exeBuffer);
125
+ const res = NtExecutableResource.from(exe);
126
+
127
+ // Find icon group
128
+ const iconGroups = Resource.IconGroupEntry.fromEntries(res.entries);
129
+
130
+ if (iconGroups.length === 0) {
131
+ throw new Error('No icon found in executable');
132
+ }
133
+
134
+ // Get first icon group
135
+ const iconGroup = iconGroups[0];
136
+ const iconFile = Resource.IconFile.from(iconGroup, res.entries);
137
+
138
+ // Write icon file
139
+ fs.writeFileSync(outputPath, Buffer.from(iconFile.data));
140
+ console.log(`Extracted icon to: ${outputPath}`);
141
+ } catch (error) {
142
+ throw new Error(`Failed to extract icon: ${error.message}`);
143
+ }
144
+ }
145
+
146
+ module.exports = {
147
+ setIcon,
148
+ validateIcon,
149
+ extractIcon
150
+ };
@@ -0,0 +1,397 @@
1
+ const { spawn } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const archiver = require('archiver');
5
+ const { bundleAssets } = require('./assets');
6
+ const { setIcon } = require('./icon');
7
+
8
+ /**
9
+ * Convert console application to GUI application (hide console window)
10
+ * @param {string} exePath - Path to the executable
11
+ */
12
+ function convertToGuiApp(exePath) {
13
+ try {
14
+ // Read the executable as a buffer
15
+ const exeBuffer = fs.readFileSync(exePath);
16
+
17
+ // Get PE signature offset (at 0x3C in DOS header)
18
+ const peOffset = exeBuffer.readUInt32LE(0x3C);
19
+
20
+ // PE signature is 4 bytes ("PE\0\0")
21
+ // COFF header is 20 bytes
22
+ // Optional header starts at peOffset + 24
23
+ // Subsystem is at offset 68 (0x44) in optional header
24
+ const subsystemOffset = peOffset + 24 + 68;
25
+
26
+ // Read current subsystem value
27
+ const currentSubsystem = exeBuffer.readUInt16LE(subsystemOffset);
28
+ console.log(` Current subsystem: ${currentSubsystem} (3=CONSOLE, 2=GUI)`);
29
+
30
+ // Set to IMAGE_SUBSYSTEM_WINDOWS_GUI (2) instead of CONSOLE (3)
31
+ if (currentSubsystem === 3) {
32
+ exeBuffer.writeUInt16LE(2, subsystemOffset);
33
+ console.log(` Changed subsystem to GUI (2)`);
34
+
35
+ // Write the modified executable
36
+ fs.writeFileSync(exePath, exeBuffer);
37
+ }
38
+ } catch (error) {
39
+ console.warn(` ⚠ Warning: Could not convert to GUI app: ${error.message}`);
40
+ console.warn(' The executable will show a console window.');
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Run pkg command using spawn
46
+ * @param {string[]} args - Arguments for pkg
47
+ * @returns {Promise<void>}
48
+ */
49
+ function runPkg(args) {
50
+ return new Promise((resolve, reject) => {
51
+ // Try to find pkg binary in node_modules
52
+ let pkgBin;
53
+ try {
54
+ const pkgPackageJson = require.resolve('@yao-pkg/pkg/package.json');
55
+ const pkgDir = path.dirname(pkgPackageJson);
56
+
57
+ // Check for different possible bin locations
58
+ const possibleBins = [
59
+ path.join(pkgDir, 'lib-es5', 'bin.js'),
60
+ path.join(pkgDir, 'lib', 'bin.js'),
61
+ path.join(pkgDir, 'bin', 'pkg.js')
62
+ ];
63
+
64
+ for (const bin of possibleBins) {
65
+ if (fs.existsSync(bin)) {
66
+ pkgBin = bin;
67
+ break;
68
+ }
69
+ }
70
+
71
+ if (!pkgBin) {
72
+ throw new Error('pkg binary not found');
73
+ }
74
+ } catch (error) {
75
+ reject(new Error(`Cannot find @yao-pkg/pkg installation: ${error.message}`));
76
+ return;
77
+ }
78
+
79
+ console.log(` Running: node ${pkgBin} ${args.join(' ')}`);
80
+
81
+ // Spawn pkg process
82
+ let stdout = '';
83
+ let stderr = '';
84
+
85
+ const pkgProcess = spawn(process.execPath, [pkgBin, ...args], {
86
+ stdio: ['inherit', 'pipe', 'pipe'],
87
+ shell: false
88
+ });
89
+
90
+ pkgProcess.stdout.on('data', (data) => {
91
+ const output = data.toString();
92
+ stdout += output;
93
+ process.stdout.write(output);
94
+ });
95
+
96
+ pkgProcess.stderr.on('data', (data) => {
97
+ const output = data.toString();
98
+ stderr += output;
99
+ process.stderr.write(output);
100
+ });
101
+
102
+ pkgProcess.on('close', (code) => {
103
+ if (code === 0) {
104
+ resolve();
105
+ } else {
106
+ const errorMsg = stderr || stdout || `pkg exited with code ${code}`;
107
+ reject(new Error(errorMsg));
108
+ }
109
+ });
110
+
111
+ pkgProcess.on('error', (error) => {
112
+ reject(error);
113
+ });
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Package an ewvjs application into a standalone executable
119
+ * @param {Object} config - Packaging configuration
120
+ * @param {string} config.entry - Entry point file path
121
+ * @param {string} config.output - Output executable name (without .exe)
122
+ * @param {string} config.assets - Assets directory path
123
+ * @param {string} config.icon - Icon file path (.ico)
124
+ * @param {string} config.name - Application name
125
+ * @param {string} config.target - Target platform (e.g., node18-win-x64)
126
+ * @param {boolean} config.compress - Whether to compress with UPX
127
+ * @param {boolean} config.includeNative - Whether to include native DLLs
128
+ * @param {string[]} config.additionalModules - Additional node modules to bundle
129
+ */
130
+ async function packageApp(config) {
131
+ const {
132
+ entry,
133
+ output,
134
+ assets,
135
+ icon,
136
+ name,
137
+ target,
138
+ compress,
139
+ includeNative = true,
140
+ additionalModules = []
141
+ } = config;
142
+
143
+ const outputDir = path.join(process.cwd(), 'dist');
144
+ const outputPath = path.join(outputDir, `${output}.exe`);
145
+
146
+ // Create output directory
147
+ if (!fs.existsSync(outputDir)) {
148
+ fs.mkdirSync(outputDir, { recursive: true });
149
+ }
150
+
151
+ console.log('📝 Configuration:');
152
+ console.log(` Entry: ${entry}`);
153
+ console.log(` Output: ${outputPath}`);
154
+ console.log(` Target: ${target}`);
155
+ console.log(` Compress: ${compress ? 'Yes' : 'No'}`);
156
+ if (assets && fs.existsSync(assets)) {
157
+ console.log(` Assets: ${assets}`);
158
+ }
159
+ if (icon) {
160
+ console.log(` Icon: ${icon}`);
161
+ }
162
+ if (additionalModules.length > 0) {
163
+ console.log(` Additional Modules: ${additionalModules.join(', ')}`);
164
+ }
165
+ console.log('');
166
+
167
+ // Step 1: Package with @yao-pkg/pkg
168
+ console.log('🔨 Step 1: Creating executable with pkg...');
169
+
170
+ const pkgArgs = [
171
+ entry,
172
+ '--target', target,
173
+ '--output', outputPath,
174
+ '--public', // Faster, includes sources
175
+ '--no-bytecode', // Skip bytecode generation for faster packaging
176
+ ];
177
+
178
+ if (compress) {
179
+ pkgArgs.push('--compress', 'GZip');
180
+ }
181
+
182
+ // Add pkg configuration for native modules
183
+ const pkgConfig = {
184
+ assets: []
185
+ };
186
+
187
+ // Include native DLLs if requested
188
+ if (includeNative) {
189
+ const ewvjsPath = require.resolve('ewvjs');
190
+ const ewvjsRoot = path.dirname(path.dirname(ewvjsPath));
191
+ const nativePath = path.join(ewvjsRoot, 'native');
192
+
193
+ if (fs.existsSync(nativePath)) {
194
+ console.log(' Including native DLLs from ewvjs...');
195
+ pkgConfig.assets.push(`${nativePath}/**/*`);
196
+ }
197
+ }
198
+
199
+ // Write temporary pkg config
200
+ const pkgConfigPath = path.join(process.cwd(), '.pkg-config.json');
201
+ fs.writeFileSync(pkgConfigPath, JSON.stringify(pkgConfig, null, 2));
202
+
203
+ try {
204
+ // Execute pkg using spawn
205
+ await runPkg(pkgArgs);
206
+ console.log(' ✓ Executable created');
207
+
208
+ // Convert to GUI application (hide console window)
209
+ console.log(' Converting to GUI application...');
210
+ convertToGuiApp(outputPath);
211
+ console.log(' ✓ Converted to GUI app (no console window)');
212
+
213
+ // Clean up temp config
214
+ if (fs.existsSync(pkgConfigPath)) {
215
+ fs.unlinkSync(pkgConfigPath);
216
+ }
217
+ } catch (error) {
218
+ // Clean up temp config on error
219
+ if (fs.existsSync(pkgConfigPath)) {
220
+ fs.unlinkSync(pkgConfigPath);
221
+ }
222
+ throw new Error(`pkg failed: ${error.message}`);
223
+ }
224
+
225
+ // Step 2: Copy native DLLs next to executable
226
+ if (includeNative) {
227
+ console.log('\n🔧 Step 2: Copying native dependencies...');
228
+ const ewvjsPath = require.resolve('ewvjs');
229
+ const ewvjsRoot = path.dirname(path.dirname(ewvjsPath));
230
+ const nativePath = path.join(ewvjsRoot, 'native');
231
+
232
+ if (fs.existsSync(nativePath)) {
233
+ const targetNativePath = path.join(outputDir, 'native');
234
+
235
+ // Copy directory recursively
236
+ copyRecursive(nativePath, targetNativePath);
237
+ console.log(` ✓ Native DLLs copied to ${targetNativePath}`);
238
+
239
+ // Copy additional node modules if specified
240
+ if (additionalModules.length > 0) {
241
+ console.log('\n 📦 Copying additional node modules...');
242
+ const targetNodeModulesPath = path.join(targetNativePath, 'node_modules');
243
+ const copiedModules = new Set();
244
+
245
+ /**
246
+ * Recursively copy module and its dependencies
247
+ */
248
+ function copyModuleWithDependencies(moduleName, depth = 0) {
249
+ const indent = ' ' + ' '.repeat(depth);
250
+
251
+ // Avoid copying the same module twice
252
+ if (copiedModules.has(moduleName)) {
253
+ return;
254
+ }
255
+
256
+ try {
257
+ // Try to resolve the module from the current project
258
+ const modulePath = require.resolve(moduleName + '/package.json', {
259
+ paths: [process.cwd()]
260
+ });
261
+ const moduleRoot = path.dirname(modulePath);
262
+ const moduleDestPath = path.join(targetNodeModulesPath, moduleName);
263
+
264
+ // Mark as copied before processing to avoid circular dependencies
265
+ copiedModules.add(moduleName);
266
+
267
+ // Copy the module
268
+ copyRecursive(moduleRoot, moduleDestPath);
269
+ console.log(`${indent}✓ Copied ${moduleName}`);
270
+
271
+ // Read package.json to get dependencies
272
+ const packageJsonPath = path.join(moduleRoot, 'package.json');
273
+ if (fs.existsSync(packageJsonPath)) {
274
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
275
+ const dependencies = {
276
+ ...packageJson.dependencies,
277
+ ...packageJson.optionalDependencies
278
+ };
279
+
280
+ // Recursively copy dependencies
281
+ if (dependencies && Object.keys(dependencies).length > 0) {
282
+ for (const depName of Object.keys(dependencies)) {
283
+ copyModuleWithDependencies(depName, depth + 1);
284
+ }
285
+ }
286
+ }
287
+ } catch (error) {
288
+ if (depth === 0) {
289
+ // Only warn for top-level modules
290
+ console.warn(`${indent}⚠ Warning: Could not find module "${moduleName}": ${error.message}`);
291
+ }
292
+ // Skip missing optional dependencies silently
293
+ }
294
+ }
295
+
296
+ // Copy each requested module with its dependencies
297
+ for (const moduleName of additionalModules) {
298
+ copyModuleWithDependencies(moduleName, 0);
299
+ }
300
+
301
+ console.log(` Total modules copied: ${copiedModules.size}`);
302
+ }
303
+ } else {
304
+ console.warn(' ⚠ Warning: Native DLLs not found in ewvjs installation');
305
+ }
306
+ }
307
+
308
+ // Step 3: Bundle assets if provided
309
+ if (assets && fs.existsSync(assets)) {
310
+ console.log('\n📦 Step 3: Bundling assets...');
311
+ const assetsOutput = path.join(outputDir, 'assets');
312
+ await bundleAssets(assets, assetsOutput);
313
+ console.log(` ✓ Assets bundled to ${assetsOutput}`);
314
+ }
315
+
316
+ // Step 4: Set icon if provided
317
+ if (icon && fs.existsSync(icon)) {
318
+ console.log('\n🎨 Step 4: Setting application icon...');
319
+ await setIcon(outputPath, icon, name);
320
+ console.log(' ✓ Icon applied');
321
+ }
322
+
323
+ // Step 5: Create ZIP archive
324
+ console.log('\n📦 Step 5: Creating archive...');
325
+ const archivePath = path.join(outputDir, `${output}.zip`);
326
+ await createArchive(outputDir, archivePath);
327
+ console.log(` ✓ Archive saved to ${archivePath}`);
328
+
329
+ return outputPath;
330
+ }
331
+
332
+ /**
333
+ * Copy directory recursively
334
+ */
335
+ function copyRecursive(src, dest) {
336
+ if (!fs.existsSync(dest)) {
337
+ fs.mkdirSync(dest, { recursive: true });
338
+ }
339
+
340
+ const entries = fs.readdirSync(src, { withFileTypes: true });
341
+
342
+ for (const entry of entries) {
343
+ const srcPath = path.join(src, entry.name);
344
+ const destPath = path.join(dest, entry.name);
345
+
346
+ if (entry.isDirectory()) {
347
+ copyRecursive(srcPath, destPath);
348
+ } else {
349
+ fs.copyFileSync(srcPath, destPath);
350
+ }
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Create a ZIP archive of the dist folder
356
+ * @param {string} sourceDir - Source directory to archive
357
+ * @param {string} outputPath - Output ZIP file path
358
+ * @returns {Promise<void>}
359
+ */
360
+ function createArchive(sourceDir, outputPath) {
361
+ return new Promise((resolve, reject) => {
362
+ const output = fs.createWriteStream(outputPath);
363
+ const archive = archiver('zip', {
364
+ zlib: { level: 9 } // Maximum compression
365
+ });
366
+
367
+ output.on('close', () => {
368
+ const sizeMB = (archive.pointer() / 1024 / 1024).toFixed(2);
369
+ console.log(` ✓ Archive created: ${sizeMB} MB`);
370
+ resolve();
371
+ });
372
+
373
+ archive.on('error', (err) => {
374
+ reject(err);
375
+ });
376
+
377
+ archive.on('warning', (err) => {
378
+ if (err.code === 'ENOENT') {
379
+ console.warn(` ⚠ Warning: ${err.message}`);
380
+ } else {
381
+ reject(err);
382
+ }
383
+ });
384
+
385
+ archive.pipe(output);
386
+
387
+ // Add all files from the source directory, excluding the output archive
388
+ const outputFileName = path.basename(outputPath);
389
+ archive.directory(sourceDir, false, (entry) => {
390
+ return entry.name !== outputFileName ? entry : false;
391
+ });
392
+
393
+ archive.finalize();
394
+ });
395
+ }
396
+
397
+ module.exports = packageApp;
@@ -0,0 +1,92 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Get available templates
6
+ * @returns {Array<{name: string, description: string}>}
7
+ */
8
+ function getAvailableTemplates() {
9
+ const templatesDir = path.join(__dirname, '..', 'templates');
10
+
11
+ const templates = [
12
+ {
13
+ name: 'basic',
14
+ description: 'Basic template - Interactive app with exposed functions (default)'
15
+ },
16
+ ];
17
+
18
+ // Filter to only existing templates
19
+ return templates.filter(t => {
20
+ return fs.existsSync(path.join(templatesDir, t.name));
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Copy template to destination
26
+ * @param {string} templateName - Template name
27
+ * @param {string} projectName - Project name
28
+ * @param {string} destination - Destination directory
29
+ */
30
+ function copyTemplate(templateName, projectName, destination) {
31
+ const templatesDir = path.join(__dirname, '..', 'templates');
32
+ const templatePath = path.join(templatesDir, templateName);
33
+
34
+ if (!fs.existsSync(templatePath)) {
35
+ throw new Error(`Template "${templateName}" not found`);
36
+ }
37
+
38
+ // Create destination directory
39
+ if (!fs.existsSync(destination)) {
40
+ fs.mkdirSync(destination, { recursive: true });
41
+ }
42
+
43
+ // Copy template files recursively
44
+ copyTemplateRecursive(templatePath, destination, projectName);
45
+ }
46
+
47
+ /**
48
+ * Copy template directory recursively and process template files
49
+ * @param {string} src - Source directory
50
+ * @param {string} dest - Destination directory
51
+ * @param {string} projectName - Project name for replacements
52
+ */
53
+ function copyTemplateRecursive(src, dest, projectName) {
54
+ const entries = fs.readdirSync(src, { withFileTypes: true });
55
+
56
+ for (const entry of entries) {
57
+ const srcPath = path.join(src, entry.name);
58
+ const destPath = path.join(dest, entry.name);
59
+
60
+ if (entry.isDirectory()) {
61
+ // Create directory and recurse
62
+ if (!fs.existsSync(destPath)) {
63
+ fs.mkdirSync(destPath, { recursive: true });
64
+ }
65
+ copyTemplateRecursive(srcPath, destPath, projectName);
66
+ } else {
67
+ // Copy and process file
68
+ let content = fs.readFileSync(srcPath, 'utf-8');
69
+
70
+ // Replace template variables
71
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
72
+
73
+ fs.writeFileSync(destPath, content, 'utf-8');
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Validate template name
80
+ * @param {string} templateName - Template name
81
+ * @returns {boolean}
82
+ */
83
+ function isValidTemplate(templateName) {
84
+ const templates = getAvailableTemplates();
85
+ return templates.some(t => t.name === templateName);
86
+ }
87
+
88
+ module.exports = {
89
+ getAvailableTemplates,
90
+ copyTemplate,
91
+ isValidTemplate
92
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "ewvjs-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for packaging ewvjs applications",
5
+ "main": "./index.js",
6
+ "bin": {
7
+ "ewvjs": "./bin/ewvjs-cli.js"
8
+ },
9
+ "files": [
10
+ "bin/**/*",
11
+ "lib/**/*",
12
+ "templates/**/*",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ },
19
+ "keywords": [
20
+ "ewvjs",
21
+ "webview",
22
+ "webview2",
23
+ "cli",
24
+ "packaging",
25
+ "electron-alternative"
26
+ ],
27
+ "author": "miukyo",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/miukyo/ewvjs",
32
+ "directory": "packages/cli"
33
+ },
34
+ "type": "commonjs",
35
+ "os": [
36
+ "win32"
37
+ ],
38
+ "dependencies": {
39
+ "@inquirer/prompts": "^8.2.0",
40
+ "@yao-pkg/pkg": "^6.12.0",
41
+ "archiver": "^7.0.1",
42
+ "commander": "^12.1.0",
43
+ "resedit": "^2.0.2"
44
+ },
45
+ "peerDependencies": {
46
+ "ewvjs": "^1.0.3"
47
+ }
48
+ }
@@ -0,0 +1,30 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A ewvjs application.
4
+
5
+ ## Getting Started
6
+
7
+ 1. Install dependencies:
8
+ ```bash
9
+ npm install
10
+ ```
11
+
12
+ 2. Run the application:
13
+ ```bash
14
+ npm start
15
+ ```
16
+
17
+ 3. Package the application:
18
+ ```bash
19
+ npm run package
20
+ ```
21
+
22
+ ## Project Structure
23
+
24
+ - `app.js` - Main application entry point
25
+ - `assets/` - Static assets (images, fonts, etc.)
26
+ - `package.json` - Project configuration
27
+
28
+ ## Documentation
29
+
30
+ Visit [ewvjs documentation](https://github.com/miukyo/ewvjs) for more information.
@@ -0,0 +1,28 @@
1
+ const { create_window, start, expose } = require('ewvjs');
2
+ const path = require('path');
3
+
4
+ expose('greet', (name) => {
5
+ return `Hello, ${name}! This is from Node.js 🚀`;
6
+ });
7
+
8
+ expose('getSystemInfo', () => {
9
+ return {
10
+ platform: process.platform,
11
+ arch: process.arch,
12
+ nodeVersion: process.version,
13
+ uptime: process.uptime()
14
+ };
15
+ });
16
+
17
+ // Create main window
18
+ const window = create_window('Hello ewvjs', `file://${path.resolve('index.html')}`, {
19
+ width: 800,
20
+ height: 600,
21
+ vibrancy: true,
22
+ debug: true
23
+ });
24
+
25
+ window.run();
26
+
27
+ // Start the event loop
28
+ start();
@@ -0,0 +1,9 @@
1
+ # Assets Directory
2
+
3
+ Place your static assets here:
4
+ - Images
5
+ - Fonts
6
+ - Additional CSS files
7
+ - Additional JavaScript files
8
+
9
+ These assets will be included when you package your application.
@@ -0,0 +1,126 @@
1
+
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Hello ewvjs</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: sans-serif;
16
+ color: white;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ min-height: 100vh;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ text-align: center;
26
+ max-width: 600px;
27
+ }
28
+
29
+ h1 {
30
+ font-size: 3em;
31
+ margin-bottom: 20px;
32
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
33
+ }
34
+
35
+ p {
36
+ font-size: 1.2em;
37
+ margin-bottom: 30px;
38
+ opacity: 0.9;
39
+ }
40
+
41
+ input {
42
+ width: 100%;
43
+ padding: 15px;
44
+ font-size: 16px;
45
+ border: none;
46
+ margin-bottom: 15px;
47
+ background: rgba(255, 255, 255, 0.9);
48
+ color: #333;
49
+ }
50
+
51
+ button {
52
+ background: white;
53
+ color: #000000;
54
+ border: none;
55
+ padding: 15px 30px;
56
+ font-size: 18px;
57
+ font-weight: bold;
58
+ cursor: pointer;
59
+ transition: all 0.3s ease;
60
+ margin: 5px;
61
+ }
62
+
63
+ button:hover {
64
+ transform: translateY(-2px);
65
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
66
+ }
67
+
68
+ button:active {
69
+ transform: translateY(0);
70
+ }
71
+
72
+ .result {
73
+ margin-top: 20px;
74
+ padding: 20px;
75
+ background: rgba(255, 255, 255, 0.15);
76
+ min-height: 60px;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ }
81
+
82
+ .info {
83
+ text-align: left;
84
+ font-size: 0.9em;
85
+ line-height: 1.6;
86
+ }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <div class="container">
91
+ <h1>🚀 Hello ewvjs!</h1>
92
+ <p>A lightweight WebView2 application for Windows</p>
93
+
94
+ <input type="text" id="nameInput" placeholder="Enter your name..." value="World">
95
+
96
+ <div>
97
+ <button onclick="sayHello()">Greet Me</button>
98
+ <button onclick="showSystemInfo()">System Info</button>
99
+ </div>
100
+
101
+ <div class="result" id="result">
102
+ Click a button to see the result...
103
+ </div>
104
+ </div>
105
+
106
+ <script>
107
+ async function sayHello() {
108
+ const name = document.getElementById('nameInput').value || 'World';
109
+ const result = await window.ewvjs.api.greet(name);
110
+ document.getElementById('result').innerHTML = '<strong>' + result + '</strong>';
111
+ }
112
+
113
+ async function showSystemInfo() {
114
+ const info = await window.ewvjs.api.getSystemInfo();
115
+ document.getElementById('result').innerHTML =
116
+ '<div class="info">' +
117
+ '<strong>System Information:</strong><br>' +
118
+ 'Platform: ' + info.platform + '<br>' +
119
+ 'Architecture: ' + info.arch + '<br>' +
120
+ 'Node.js: ' + info.nodeVersion + '<br>' +
121
+ 'Uptime: ' + Math.floor(info.uptime) + ' seconds' +
122
+ '</div>';
123
+ }
124
+ </script>
125
+ </body>
126
+ </html>
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "description": "My ewvjs application",
5
+ "main": "app.js",
6
+ "scripts": {
7
+ "start": "node app.js",
8
+ "package": "ewvjs package app.js -o myapp -n \"My App\""
9
+ },
10
+ "dependencies": {
11
+ "ewvjs": "^1.0.0",
12
+ "ewvjs-cli": "^1.0.0"
13
+ }
14
+ }