fazer-lang 2.2.1 → 2.4.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/README.md +43 -42
- package/docs/README.md +26 -0
- package/docs/examples.md +60 -0
- package/docs/getting-started.md +153 -0
- package/docs/stdlib.md +111 -0
- package/docs/syntax.md +86 -0
- package/fazer.js +521 -7
- package/package.json +13 -5
- package/tools/announce.fz +48 -0
- package/tools/builder.js +208 -0
package/tools/builder.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { execSync, spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
function log(msg) { console.log(`[Fazer Build] ${msg}`); }
|
|
7
|
+
function error(msg) { console.error(`[Error] ${msg}`); process.exit(1); }
|
|
8
|
+
|
|
9
|
+
module.exports = async function build(inputFile, args) {
|
|
10
|
+
if (!inputFile) error("No input file specified. Usage: fazer build <app.fz>");
|
|
11
|
+
|
|
12
|
+
const inputPath = path.resolve(inputFile);
|
|
13
|
+
if (!fs.existsSync(inputPath)) error(`Input file not found: ${inputPath}`);
|
|
14
|
+
|
|
15
|
+
const appName = path.basename(inputFile, '.fz');
|
|
16
|
+
const distDir = path.resolve(process.cwd(), 'dist', appName);
|
|
17
|
+
|
|
18
|
+
// Parse args
|
|
19
|
+
let iconPath = null;
|
|
20
|
+
for(let i=0; i<args.length; i++) {
|
|
21
|
+
if (args[i] === '--icon' && args[i+1]) {
|
|
22
|
+
iconPath = path.resolve(args[i+1]);
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log(`Building '${appName}'...`);
|
|
28
|
+
log(`Output Directory: ${distDir}`);
|
|
29
|
+
|
|
30
|
+
// 1. Prepare Directory
|
|
31
|
+
if (fs.existsSync(distDir)) {
|
|
32
|
+
fs.rmSync(distDir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
// 2. Copy Core Files
|
|
37
|
+
const fazerRoot = path.dirname(__dirname); // tools/.. -> fazer-lang/
|
|
38
|
+
const fazerJsPath = path.join(fazerRoot, 'fazer.js');
|
|
39
|
+
|
|
40
|
+
fs.copyFileSync(fazerJsPath, path.join(distDir, 'fazer.js'));
|
|
41
|
+
fs.copyFileSync(inputPath, path.join(distDir, 'app.fz'));
|
|
42
|
+
|
|
43
|
+
// 3. Copy node_modules (Optimized: only copy chevrotain/ws if possible, but full copy is safer)
|
|
44
|
+
const nodeModulesSrc = path.join(fazerRoot, 'node_modules');
|
|
45
|
+
if (fs.existsSync(nodeModulesSrc)) {
|
|
46
|
+
log("Copying dependencies...");
|
|
47
|
+
// Use robocopy on Windows for speed, or recursive copy
|
|
48
|
+
try {
|
|
49
|
+
// Recursive copy
|
|
50
|
+
fs.cpSync(nodeModulesSrc, path.join(distDir, 'node_modules'), { recursive: true });
|
|
51
|
+
} catch(e) {
|
|
52
|
+
log("Warning: Failed to copy node_modules. You may need to run 'npm install' in dist.");
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
log("Warning: node_modules not found. The app might not run without dependencies.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 4. Generate Launcher (C#)
|
|
59
|
+
log("Generating Native Launcher...");
|
|
60
|
+
|
|
61
|
+
// We embed the logic to find 'node'
|
|
62
|
+
// If we want to be truly portable, we should copy node.exe here too.
|
|
63
|
+
// For now, we assume 'node' is in PATH or next to the exe.
|
|
64
|
+
|
|
65
|
+
const launcherCs = `
|
|
66
|
+
using System;
|
|
67
|
+
using System.Diagnostics;
|
|
68
|
+
using System.IO;
|
|
69
|
+
using System.Windows.Forms;
|
|
70
|
+
|
|
71
|
+
class Program {
|
|
72
|
+
[STAThread]
|
|
73
|
+
static void Main() {
|
|
74
|
+
string appDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
75
|
+
string script = Path.Combine(appDir, "fazer.js");
|
|
76
|
+
string app = Path.Combine(appDir, "app.fz");
|
|
77
|
+
|
|
78
|
+
string nodeExe = "node";
|
|
79
|
+
if (File.Exists(Path.Combine(appDir, "node.exe"))) {
|
|
80
|
+
nodeExe = Path.Combine(appDir, "node.exe");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
string extraArgs = "";
|
|
84
|
+
string[] cmdArgs = Environment.GetCommandLineArgs();
|
|
85
|
+
// Skip first arg which is the executable itself
|
|
86
|
+
for (int i = 1; i < cmdArgs.Length; i++) {
|
|
87
|
+
extraArgs += " \\\"" + cmdArgs[i] + "\\\"";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ProcessStartInfo psi = new ProcessStartInfo();
|
|
91
|
+
psi.FileName = nodeExe;
|
|
92
|
+
// Arguments: "path/to/fazer.js" "path/to/app.fz" extraArgs
|
|
93
|
+
psi.Arguments = "\\\"" + script + "\\\" \\\"" + app + "\\\" " + extraArgs;
|
|
94
|
+
|
|
95
|
+
psi.UseShellExecute = false;
|
|
96
|
+
psi.CreateNoWindow = true;
|
|
97
|
+
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
Process p = Process.Start(psi);
|
|
101
|
+
} catch (Exception e) {
|
|
102
|
+
MessageBox.Show("Failed to launch Fazer App:\\n" + e.Message + "\\n\\nEnsure Node.js is installed or node.exe is in the folder.", "Launch Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const csPath = path.join(distDir, 'Launcher.cs');
|
|
109
|
+
fs.writeFileSync(csPath, launcherCs);
|
|
110
|
+
|
|
111
|
+
// 5. Compile Launcher
|
|
112
|
+
// We use C# compiler (csc.exe) which is available on most Windows
|
|
113
|
+
// Or we use PowerShell Add-Type hack to compile to exe?
|
|
114
|
+
// Add-Type -OutputAssembly is easiest from PS.
|
|
115
|
+
|
|
116
|
+
const exeName = `${appName}.exe`;
|
|
117
|
+
const exePath = path.join(distDir, exeName);
|
|
118
|
+
|
|
119
|
+
let iconArg = "";
|
|
120
|
+
if (iconPath && fs.existsSync(iconPath)) {
|
|
121
|
+
// Copy icon to dist
|
|
122
|
+
const distIcon = path.join(distDir, 'app.ico');
|
|
123
|
+
fs.copyFileSync(iconPath, distIcon);
|
|
124
|
+
iconArg = `-Win32Icon "${distIcon}"`; // PowerShell param
|
|
125
|
+
// For csc: /win32icon:app.ico
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
log("Compiling EXE...");
|
|
129
|
+
|
|
130
|
+
// We use PowerShell to compile because locating csc.exe can be annoying
|
|
131
|
+
// Add-Type -TypeDefinition $code -OutputAssembly $out -Target WinExe ...
|
|
132
|
+
|
|
133
|
+
const psScript = `
|
|
134
|
+
$code = Get-Content -Raw "${csPath}"
|
|
135
|
+
$params = @{
|
|
136
|
+
TypeDefinition = $code
|
|
137
|
+
OutputAssembly = "${exePath}"
|
|
138
|
+
Target = "WinExe"
|
|
139
|
+
ReferencedAssemblies = "System.Windows.Forms"
|
|
140
|
+
}
|
|
141
|
+
${iconArg ? `$compilerOptions = New-Object System.CodeDom.Compiler.CompilerParameters
|
|
142
|
+
$compilerOptions.CompilerOptions = "/win32icon:${path.join(distDir, 'app.ico').replace(/\\/g, '\\\\')}"
|
|
143
|
+
# Add-Type doesn't easily support CompilerOptions for icons in all versions.
|
|
144
|
+
# Fallback to direct csc call if needed or use specific Add-Type overload.
|
|
145
|
+
` : ''}
|
|
146
|
+
|
|
147
|
+
# Simple compilation without icon for now via Add-Type,
|
|
148
|
+
# but for Icon we usually need csc. Let's try to find csc.
|
|
149
|
+
|
|
150
|
+
$csc = (Get-ChildItem -Path "$env:windir\\Microsoft.NET\\Framework64\\v4*" -Filter csc.exe | Select-Object -Last 1).FullName
|
|
151
|
+
if (-not $csc) {
|
|
152
|
+
$csc = (Get-ChildItem -Path "$env:windir\\Microsoft.NET\\Framework\\v4*" -Filter csc.exe | Select-Object -Last 1).FullName
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if ($csc) {
|
|
156
|
+
Write-Host "Compiling with CSC: $csc"
|
|
157
|
+
$args = @("/target:winexe", "/out:${exePath}", "${csPath}")
|
|
158
|
+
if ("${iconArg}") { $args += "/win32icon:${path.join(distDir, 'app.ico')}" }
|
|
159
|
+
& $csc $args
|
|
160
|
+
} else {
|
|
161
|
+
Write-Host "CSC not found, using CSharpCodeProvider..."
|
|
162
|
+
$codeProvider = New-Object Microsoft.CSharp.CSharpCodeProvider
|
|
163
|
+
$parameters = New-Object System.CodeDom.Compiler.CompilerParameters
|
|
164
|
+
$parameters.GenerateExecutable = $true
|
|
165
|
+
$parameters.OutputAssembly = "${exePath}"
|
|
166
|
+
$parameters.CompilerOptions = "/target:winexe"
|
|
167
|
+
$parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll")
|
|
168
|
+
$parameters.ReferencedAssemblies.Add("System.dll")
|
|
169
|
+
$parameters.ReferencedAssemblies.Add("System.Drawing.dll")
|
|
170
|
+
|
|
171
|
+
if ("${iconArg}") {
|
|
172
|
+
$parameters.CompilerOptions += " /win32icon:${path.join(distDir, 'app.ico').replace(/\\/g, '\\\\')}"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
$results = $codeProvider.CompileAssemblyFromSource($parameters, $code)
|
|
176
|
+
|
|
177
|
+
if ($results.Errors.HasErrors) {
|
|
178
|
+
foreach($e in $results.Errors) {
|
|
179
|
+
Write-Error $e.ToString()
|
|
180
|
+
}
|
|
181
|
+
exit 1
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
// Write PS build script
|
|
187
|
+
const psBuildPath = path.join(distDir, 'build_exe.ps1');
|
|
188
|
+
fs.writeFileSync(psBuildPath, psScript);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${psBuildPath}"`, { stdio: 'inherit' });
|
|
192
|
+
} catch(e) {
|
|
193
|
+
log("Compilation failed. See error above.");
|
|
194
|
+
// Clean up temp files
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Clean up
|
|
199
|
+
if (fs.existsSync(exePath)) {
|
|
200
|
+
fs.unlinkSync(csPath);
|
|
201
|
+
fs.unlinkSync(psBuildPath);
|
|
202
|
+
log("Build Success!");
|
|
203
|
+
log(`Created: ${exePath}`);
|
|
204
|
+
log("You can now zip the folder '${distDir}' and share it.");
|
|
205
|
+
} else {
|
|
206
|
+
error("EXE file was not created.");
|
|
207
|
+
}
|
|
208
|
+
};
|