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 +21 -0
- package/README.md +101 -0
- package/bin/ewvjs-cli.js +174 -0
- package/lib/assets.js +129 -0
- package/lib/icon.js +150 -0
- package/lib/packager.js +397 -0
- package/lib/templates.js +92 -0
- package/package.json +48 -0
- package/templates/basic/README.md +30 -0
- package/templates/basic/app.js +28 -0
- package/templates/basic/assets/.gitkeep +9 -0
- package/templates/basic/index.html +126 -0
- package/templates/basic/package.json +14 -0
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
|
package/bin/ewvjs-cli.js
ADDED
|
@@ -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
|
+
};
|
package/lib/packager.js
ADDED
|
@@ -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;
|
package/lib/templates.js
ADDED
|
@@ -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,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
|
+
}
|