@5ive-tech/cli 1.0.4
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 +226 -0
- package/dist/assets/vm/five_vm_wasm.d.ts +762 -0
- package/dist/assets/vm/five_vm_wasm.js +3754 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +247 -0
- package/dist/assets/vm/package.json +11 -0
- package/dist/cli.d.ts +47 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +343 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +3 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +872 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +431 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy-and-execute.d.ts +3 -0
- package/dist/commands/deploy-and-execute.d.ts.map +1 -0
- package/dist/commands/deploy-and-execute.js +317 -0
- package/dist/commands/deploy-and-execute.js.map +1 -0
- package/dist/commands/deploy.d.ts +21 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +806 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/donate.d.ts +4 -0
- package/dist/commands/donate.d.ts.map +1 -0
- package/dist/commands/donate.js +104 -0
- package/dist/commands/donate.js.map +1 -0
- package/dist/commands/execute.d.ts +6 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +749 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/fmt.d.ts +3 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +327 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +224 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +45 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +119 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +887 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/local.d.ts +3 -0
- package/dist/commands/local.d.ts.map +1 -0
- package/dist/commands/local.js +703 -0
- package/dist/commands/local.js.map +1 -0
- package/dist/commands/namespace.d.ts +3 -0
- package/dist/commands/namespace.d.ts.map +1 -0
- package/dist/commands/namespace.js +328 -0
- package/dist/commands/namespace.js.map +1 -0
- package/dist/commands/template.d.ts +4 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +486 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +890 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/version.d.ts +6 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +339 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +69 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +261 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +21 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +35 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +105 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/project/ProjectLoader.d.ts +12 -0
- package/dist/project/ProjectLoader.d.ts.map +1 -0
- package/dist/project/ProjectLoader.js +115 -0
- package/dist/project/ProjectLoader.js.map +1 -0
- package/dist/types.d.ts +334 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/AccountFixtureGenerator.d.ts +48 -0
- package/dist/utils/AccountFixtureGenerator.d.ts.map +1 -0
- package/dist/utils/AccountFixtureGenerator.js +265 -0
- package/dist/utils/AccountFixtureGenerator.js.map +1 -0
- package/dist/utils/FiveFileManager.d.ts +96 -0
- package/dist/utils/FiveFileManager.d.ts.map +1 -0
- package/dist/utils/FiveFileManager.js +329 -0
- package/dist/utils/FiveFileManager.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +72 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +314 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/cli-ui.d.ts +39 -0
- package/dist/utils/cli-ui.d.ts.map +1 -0
- package/dist/utils/cli-ui.js +75 -0
- package/dist/utils/cli-ui.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +25 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +50 -0
- package/dist/utils/fileUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +53 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +287 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/compiler.d.ts +101 -0
- package/dist/wasm/compiler.d.ts.map +1 -0
- package/dist/wasm/compiler.js +906 -0
- package/dist/wasm/compiler.js.map +1 -0
- package/dist/wasm/loader.d.ts +2 -0
- package/dist/wasm/loader.d.ts.map +1 -0
- package/dist/wasm/loader.js +90 -0
- package/dist/wasm/loader.js.map +1 -0
- package/dist/wasm/vm.d.ts +32 -0
- package/dist/wasm/vm.d.ts.map +1 -0
- package/dist/wasm/vm.js +440 -0
- package/dist/wasm/vm.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
// Compile command.
|
|
2
|
+
import { readFile, writeFile, stat, mkdir } from 'fs/promises';
|
|
3
|
+
import { join, dirname, extname, basename, isAbsolute, resolve } from 'path';
|
|
4
|
+
import { glob } from 'glob';
|
|
5
|
+
import { FiveSDK, TypeGenerator } from '@5ive-tech/sdk';
|
|
6
|
+
import { computeHash, loadProjectConfig, writeBuildManifest } from '../project/ProjectLoader.js';
|
|
7
|
+
import { success as uiSuccess, error as uiError, section } from '../utils/cli-ui.js';
|
|
8
|
+
export const compileCommand = {
|
|
9
|
+
name: 'compile',
|
|
10
|
+
description: 'Compile Five source to bytecode',
|
|
11
|
+
aliases: ['c'],
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
flags: '-o, --output <file>',
|
|
15
|
+
description: 'Output file path (default: <input>.five)',
|
|
16
|
+
required: false
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
flags: '-t, --target <target>',
|
|
20
|
+
description: 'Compilation target',
|
|
21
|
+
choices: ['vm', 'solana', 'debug', 'test'],
|
|
22
|
+
defaultValue: 'vm'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
flags: '-O, --optimize [level]',
|
|
26
|
+
description: 'Enable optimizations (0-3, default: 2)',
|
|
27
|
+
defaultValue: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
flags: '--debug',
|
|
31
|
+
description: 'Include debug information in output',
|
|
32
|
+
defaultValue: false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
flags: '--abi <file>',
|
|
36
|
+
description: 'Generate ABI file',
|
|
37
|
+
required: false
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
flags: '--analyze',
|
|
41
|
+
description: 'Perform bytecode analysis and show report',
|
|
42
|
+
defaultValue: false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
flags: '--watch',
|
|
46
|
+
description: 'Watch for file changes and recompile',
|
|
47
|
+
defaultValue: false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
flags: '--validate',
|
|
51
|
+
description: 'Validate syntax without compilation',
|
|
52
|
+
defaultValue: false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
flags: '--metrics-output <file>',
|
|
56
|
+
description: 'Write compilation metrics to a file',
|
|
57
|
+
required: false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
flags: '--metrics-format <format>',
|
|
61
|
+
description: 'Metrics export format',
|
|
62
|
+
choices: ['json', 'csv', 'toml'],
|
|
63
|
+
defaultValue: 'json'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
flags: '--error-format <format>',
|
|
67
|
+
description: 'Error output format',
|
|
68
|
+
choices: ['terminal', 'json', 'lsp'],
|
|
69
|
+
defaultValue: 'terminal'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
flags: '--project <path>',
|
|
73
|
+
description: 'Project directory or five.toml path',
|
|
74
|
+
required: false
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
flags: '--flat-namespace',
|
|
78
|
+
description: 'Use flat namespace (no module prefixes) for backward compatibility',
|
|
79
|
+
defaultValue: false
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
arguments: [
|
|
83
|
+
{
|
|
84
|
+
name: 'input',
|
|
85
|
+
description: 'Input Five source file(s) or glob pattern (optional with five.toml)',
|
|
86
|
+
required: false,
|
|
87
|
+
variadic: true
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
examples: [
|
|
91
|
+
{
|
|
92
|
+
command: 'five compile src/main.v',
|
|
93
|
+
description: 'Compile a single Five source file'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
command: 'five compile src/**/*.v -o build/',
|
|
97
|
+
description: 'Compile all Five files in src directory'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
command: 'five compile src/main.v -t solana -O 3 --abi main.abi.json',
|
|
101
|
+
description: 'Compile for Solana with maximum optimization and ABI generation'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
command: 'five compile src/main.v --analyze --debug',
|
|
105
|
+
description: 'Compile with debug info and bytecode analysis'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
command: 'five compile src/**/*.v --watch',
|
|
109
|
+
description: 'Watch for changes and auto-recompile'
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
handler: async (args, options, context) => {
|
|
113
|
+
const { logger } = context;
|
|
114
|
+
try {
|
|
115
|
+
// Load project configuration if available
|
|
116
|
+
const projectContext = await loadProjectConfig(options.project, process.cwd());
|
|
117
|
+
if (context.options.verbose) {
|
|
118
|
+
logger.info(projectContext
|
|
119
|
+
? `Using project config at ${projectContext.configPath}`
|
|
120
|
+
: 'No project config found (continuing with CLI arguments)');
|
|
121
|
+
}
|
|
122
|
+
// Initialize SDK silently
|
|
123
|
+
// Resolve input files - args might be [inputs, options] or just inputs depending on how CLI framework calls it
|
|
124
|
+
let inputArgs = args;
|
|
125
|
+
if (Array.isArray(args) && args.length > 0) {
|
|
126
|
+
// If first element is an array (the actual inputs) and second is an object (options),
|
|
127
|
+
// then args was passed as [inputs, options]
|
|
128
|
+
if (Array.isArray(args[0]) && (typeof args[1] === 'object' && !Array.isArray(args[1]))) {
|
|
129
|
+
inputArgs = args[0];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Add auto-discover flag to options for resolution
|
|
133
|
+
const resolveOptions = {
|
|
134
|
+
...options,
|
|
135
|
+
autoDiscover: options.autoDiscover // Assuming this is passed from CLI args
|
|
136
|
+
};
|
|
137
|
+
const { files: inputFiles, mode } = await resolveInputFiles(inputArgs, resolveOptions, logger, projectContext);
|
|
138
|
+
if (inputFiles.length === 0) {
|
|
139
|
+
throw new Error('No Five source files found. Provide input paths, or run from a project with five.toml (entry_point/source_dir), or pass --project <path>.');
|
|
140
|
+
}
|
|
141
|
+
// Only show file count in verbose mode
|
|
142
|
+
if (context.options.verbose) {
|
|
143
|
+
logger.info(`Found ${inputFiles.length} source file(s) to compile (mode: ${mode})`);
|
|
144
|
+
}
|
|
145
|
+
// Handle different modes
|
|
146
|
+
if (options.validate) {
|
|
147
|
+
await validateFiles(inputFiles, context);
|
|
148
|
+
}
|
|
149
|
+
else if (options.watch) {
|
|
150
|
+
await watchAndCompile(inputFiles, options, context, projectContext);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// If multi-file mode is detected, use compileMultiProject
|
|
154
|
+
if (mode === 'multi-auto' || mode === 'multi-explicit' || projectContext?.config.multiFileMode) {
|
|
155
|
+
// For multi-file compilation, pass all files including dependencies
|
|
156
|
+
// If auto-discover was used, inputFiles already contains all discovered modules
|
|
157
|
+
if (projectContext) {
|
|
158
|
+
await compileMultiProject(inputFiles, options, context, projectContext);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
await compileFiles(inputFiles, options, context, projectContext);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
logger.error('Compilation failed:', error);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Resolve input files from arguments and glob patterns
|
|
174
|
+
*/
|
|
175
|
+
async function resolveInputFiles(args, options, logger, projectContext) {
|
|
176
|
+
const inputFiles = [];
|
|
177
|
+
// If no args and project config exists, derive from config
|
|
178
|
+
if ((!args || args.length === 0) && projectContext) {
|
|
179
|
+
const root = projectContext.rootDir;
|
|
180
|
+
const cfg = projectContext.config;
|
|
181
|
+
// If modules are defined in config, use them
|
|
182
|
+
if (cfg.modules && Object.keys(cfg.modules).length > 0) {
|
|
183
|
+
const moduleFiles = [];
|
|
184
|
+
const entryPoint = cfg.entryPoint ? join(root, cfg.entryPoint) : null;
|
|
185
|
+
if (entryPoint) {
|
|
186
|
+
moduleFiles.push(entryPoint);
|
|
187
|
+
}
|
|
188
|
+
for (const [_, files] of Object.entries(cfg.modules)) {
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
const absPath = isAbsolute(file) ? file : join(root, file);
|
|
191
|
+
if (absPath !== entryPoint) {
|
|
192
|
+
moduleFiles.push(absPath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// If we have an entry point or modules, return valid configuration
|
|
197
|
+
if (moduleFiles.length > 0) {
|
|
198
|
+
// Ensure unique files
|
|
199
|
+
const uniqueFiles = [...new Set(moduleFiles)];
|
|
200
|
+
if (logger && logger.debug) {
|
|
201
|
+
logger.debug(`Resolved ${uniqueFiles.length} files from five.toml [modules] section`);
|
|
202
|
+
}
|
|
203
|
+
return { files: uniqueFiles, mode: 'multi-explicit' };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const multiFileMode = typeof cfg.multiFileMode === 'string' ? cfg.multiFileMode : 'disabled';
|
|
207
|
+
if (multiFileMode !== 'disabled' && !cfg.entryPoint) {
|
|
208
|
+
throw new Error('multi_file_mode is enabled but entry_point is not set in five.toml');
|
|
209
|
+
}
|
|
210
|
+
if (cfg.entryPoint) {
|
|
211
|
+
const entryPoint = join(root, cfg.entryPoint);
|
|
212
|
+
// Auto discovery mode from config
|
|
213
|
+
if (multiFileMode === 'auto') {
|
|
214
|
+
// Dynamically import to avoid circular dependency issues
|
|
215
|
+
const { FiveCompilerWasm } = await import('../wasm/compiler.js');
|
|
216
|
+
const compiler = new FiveCompilerWasm(logger);
|
|
217
|
+
await compiler.initialize();
|
|
218
|
+
const discovered = await compiler.discoverModules(entryPoint);
|
|
219
|
+
return { files: [entryPoint, ...discovered], mode: 'multi-auto' };
|
|
220
|
+
}
|
|
221
|
+
inputFiles.push(entryPoint);
|
|
222
|
+
// NEW: Auto-detect multi-file mode like five-frontend-2
|
|
223
|
+
// Scan for all .v files in sourceDir and use multi-file if multiple files found
|
|
224
|
+
const pattern = join(root, cfg.sourceDir || 'src', '**/*.v');
|
|
225
|
+
const allVFiles = await glob(pattern);
|
|
226
|
+
const uniqueVFiles = [...new Set(allVFiles)];
|
|
227
|
+
// If multiple .v files exist, use multi-file compilation
|
|
228
|
+
// But only if we didn't use modules section (implicit vs explicit)
|
|
229
|
+
if (uniqueVFiles.length > 1) {
|
|
230
|
+
if (logger && logger.debug) {
|
|
231
|
+
logger.debug(`Detected ${uniqueVFiles.length} .v files - using multi-file compilation mode`);
|
|
232
|
+
}
|
|
233
|
+
// Return all files with entry point first
|
|
234
|
+
const filesWithEntry = [entryPoint, ...uniqueVFiles.filter(f => f !== entryPoint)];
|
|
235
|
+
return { files: [...new Set(filesWithEntry)], mode: 'multi-explicit' };
|
|
236
|
+
}
|
|
237
|
+
return { files: inputFiles, mode: 'single' };
|
|
238
|
+
}
|
|
239
|
+
// default: compile all .v files in sourceDir
|
|
240
|
+
const pattern = join(root, cfg.sourceDir, '**/*.v');
|
|
241
|
+
const files = await glob(pattern);
|
|
242
|
+
inputFiles.push(...files);
|
|
243
|
+
// If multiple files found, use multi-file mode
|
|
244
|
+
const uniqueFiles = [...new Set(inputFiles)];
|
|
245
|
+
const mode = uniqueFiles.length > 1 ? 'multi-explicit' : 'single';
|
|
246
|
+
return { files: uniqueFiles, mode };
|
|
247
|
+
}
|
|
248
|
+
// Check for CLI flags
|
|
249
|
+
if (options.autoDiscover) {
|
|
250
|
+
// Auto-discover mode requires a single entry point
|
|
251
|
+
if (args.length !== 1) {
|
|
252
|
+
throw new Error('--auto-discover requires exactly one entry point file');
|
|
253
|
+
}
|
|
254
|
+
const entryPoint = args[0];
|
|
255
|
+
// Dynamically import to avoid circular dependency issues
|
|
256
|
+
const { FiveCompilerWasm } = await import('../wasm/compiler.js');
|
|
257
|
+
const compiler = new FiveCompilerWasm(logger);
|
|
258
|
+
await compiler.initialize();
|
|
259
|
+
const discovered = await compiler.discoverModules(entryPoint);
|
|
260
|
+
// Return entry point + discovered modules
|
|
261
|
+
return { files: [entryPoint, ...discovered], mode: 'multi-auto' };
|
|
262
|
+
}
|
|
263
|
+
for (const arg of args) {
|
|
264
|
+
try {
|
|
265
|
+
const stats = await stat(arg);
|
|
266
|
+
if (stats.isFile() && extname(arg) === '.v') {
|
|
267
|
+
inputFiles.push(arg);
|
|
268
|
+
}
|
|
269
|
+
else if (stats.isDirectory()) {
|
|
270
|
+
// Search for .v files in directory
|
|
271
|
+
const files = await glob(join(arg, '**/*.v'));
|
|
272
|
+
inputFiles.push(...files);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Try as glob pattern
|
|
277
|
+
const files = await glob(arg);
|
|
278
|
+
const fiveFiles = files.filter(file => extname(file) === '.v');
|
|
279
|
+
inputFiles.push(...fiveFiles);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const uniqueFiles = [...new Set(inputFiles)];
|
|
283
|
+
// If multiple files explicitly provided, treat as multi-explicit
|
|
284
|
+
if (uniqueFiles.length > 1) {
|
|
285
|
+
return { files: uniqueFiles, mode: 'multi-explicit' };
|
|
286
|
+
}
|
|
287
|
+
return { files: uniqueFiles, mode: 'single' };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Validate files without compilation using Five SDK
|
|
291
|
+
*/
|
|
292
|
+
async function validateFiles(inputFiles, context) {
|
|
293
|
+
const { logger } = context;
|
|
294
|
+
let allValid = true;
|
|
295
|
+
for (const inputFile of inputFiles) {
|
|
296
|
+
try {
|
|
297
|
+
const sourceCode = await readFile(inputFile, 'utf8');
|
|
298
|
+
// Use Five SDK to validate bytecode
|
|
299
|
+
const result = await FiveSDK.compile(sourceCode, { debug: false });
|
|
300
|
+
if (result.success) {
|
|
301
|
+
console.log(uiSuccess(`${inputFile} - valid syntax`));
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
console.log(uiError(`${inputFile} - syntax errors`));
|
|
305
|
+
if (result.formattedErrorsTerminal) {
|
|
306
|
+
console.log(result.formattedErrorsTerminal);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
result.errors?.forEach(error => {
|
|
310
|
+
console.log(` - ${error.message}`);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
allValid = false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
console.log(uiError(`${inputFile} - failed to read: ${error}`));
|
|
318
|
+
allValid = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (!allValid) {
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Compile multiple files
|
|
327
|
+
*/
|
|
328
|
+
async function compileFiles(inputFiles, options, context, projectContext) {
|
|
329
|
+
const { logger } = context;
|
|
330
|
+
const showPerFile = context.options.verbose || inputFiles.length === 1;
|
|
331
|
+
if (projectContext?.config.multiFileMode) {
|
|
332
|
+
await compileMultiProject(inputFiles, options, context, projectContext);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const results = [];
|
|
336
|
+
for (const inputFile of inputFiles) {
|
|
337
|
+
const startTime = Date.now();
|
|
338
|
+
try {
|
|
339
|
+
if (context.options.verbose) {
|
|
340
|
+
logger.info(`Compiling ${inputFile}...`);
|
|
341
|
+
}
|
|
342
|
+
const result = await compileSingleFile(inputFile, options, context, projectContext, inputFiles);
|
|
343
|
+
const duration = Date.now() - startTime;
|
|
344
|
+
results.push({ file: inputFile, success: result.success, duration });
|
|
345
|
+
if (result.success) {
|
|
346
|
+
if (showPerFile) {
|
|
347
|
+
console.log(uiSuccess(inputFile));
|
|
348
|
+
console.log(` ${result.metrics.sourceSize} chars -> ${result.metrics.bytecodeSize} bytes (${duration}ms)`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
console.log(uiError(`${inputFile} failed`));
|
|
353
|
+
// Display compilation errors (enhanced error system)
|
|
354
|
+
if (context.options.verbose) {
|
|
355
|
+
console.log("DEBUG: formattedErrorsTerminal prop:", result.formattedErrorsTerminal);
|
|
356
|
+
}
|
|
357
|
+
if (result.formattedErrorsTerminal) {
|
|
358
|
+
console.log(result.formattedErrorsTerminal);
|
|
359
|
+
}
|
|
360
|
+
else if (result.errors) {
|
|
361
|
+
for (const error of result.errors) {
|
|
362
|
+
// Check if this is an enhanced error with rich information
|
|
363
|
+
if (error.code && error.severity) {
|
|
364
|
+
const enhancedError = error;
|
|
365
|
+
// Display the error with code and category
|
|
366
|
+
console.error(` Error ${enhancedError.code}: ${enhancedError.message}`);
|
|
367
|
+
// Display description if available
|
|
368
|
+
if (enhancedError.description) {
|
|
369
|
+
console.error(` ${enhancedError.description}`);
|
|
370
|
+
}
|
|
371
|
+
// Display source location if available
|
|
372
|
+
if (enhancedError.location) {
|
|
373
|
+
const loc = enhancedError.location;
|
|
374
|
+
console.error(` at line ${loc.line}, column ${loc.column}${loc.file ? ` in ${loc.file}` : ''}`);
|
|
375
|
+
}
|
|
376
|
+
// Display suggestions if available
|
|
377
|
+
if (enhancedError.suggestions && enhancedError.suggestions.length > 0) {
|
|
378
|
+
console.error(` Suggestions:`);
|
|
379
|
+
for (const suggestion of enhancedError.suggestions) {
|
|
380
|
+
console.error(` - ${suggestion.message}`);
|
|
381
|
+
if (suggestion.code_suggestion) {
|
|
382
|
+
console.error(` try: ${suggestion.code_suggestion}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// Basic error display fallback
|
|
389
|
+
console.error(` Error: ${error.message}`);
|
|
390
|
+
if (error.sourceLocation) {
|
|
391
|
+
console.error(` at ${error.sourceLocation}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
const duration = Date.now() - startTime;
|
|
400
|
+
results.push({ file: inputFile, success: false, duration });
|
|
401
|
+
console.log(uiError(`${inputFile} failed`));
|
|
402
|
+
// Handle different error types
|
|
403
|
+
if (error instanceof Error) {
|
|
404
|
+
console.error(` Error: ${error.message}`);
|
|
405
|
+
if (error.stack) {
|
|
406
|
+
logger.debug(`Error stack: ${error.stack}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.error(` Error: ${String(error)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Summary
|
|
415
|
+
const successful = results.filter(r => r.success).length;
|
|
416
|
+
const failed = results.length - successful;
|
|
417
|
+
const totalTime = results.reduce((sum, r) => sum + r.duration, 0);
|
|
418
|
+
// Only show summary for multiple files or if verbose
|
|
419
|
+
if (results.length > 1 || context.options.verbose) {
|
|
420
|
+
console.log('\n' + section('Summary'));
|
|
421
|
+
console.log(` OK: ${successful}${failed > 0 ? ` | Failed: ${failed}` : ''}`);
|
|
422
|
+
if (context.options.verbose) {
|
|
423
|
+
console.log(` Total: ${totalTime}ms`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (failed > 0) {
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Compile a single file using Five SDK
|
|
432
|
+
*/
|
|
433
|
+
async function compileSingleFile(inputFile, options, context, projectContext, sourceFiles) {
|
|
434
|
+
const { logger } = context;
|
|
435
|
+
// Read source code
|
|
436
|
+
const sourceCode = await readFile(inputFile, 'utf8');
|
|
437
|
+
// Determine output file - default to .five format
|
|
438
|
+
const outputFile = options.output ||
|
|
439
|
+
getDefaultOutputPath(inputFile, options.target || projectContext?.config.target || 'vm', true, projectContext?.config, projectContext?.rootDir);
|
|
440
|
+
// Ensure output directory exists
|
|
441
|
+
await mkdir(dirname(outputFile), { recursive: true });
|
|
442
|
+
// Prepare SDK compilation options
|
|
443
|
+
const compilationOptions = {
|
|
444
|
+
optimize: parseOptimizationLevel(options.optimize),
|
|
445
|
+
debug: Boolean(options.debug),
|
|
446
|
+
optimizationLevel: 'production',
|
|
447
|
+
includeMetrics: Boolean(options.metricsOutput),
|
|
448
|
+
metricsFormat: options.metricsFormat || 'json',
|
|
449
|
+
errorFormat: options.errorFormat || 'terminal',
|
|
450
|
+
comprehensiveMetrics: Boolean(options.comprehensiveMetrics),
|
|
451
|
+
metricsOutput: options.metricsOutput,
|
|
452
|
+
flatNamespace: Boolean(options.flatNamespace)
|
|
453
|
+
};
|
|
454
|
+
// Compile using Five SDK (includes .five format generation)
|
|
455
|
+
// Compile using FiveCompilerWasm directly for rich errors
|
|
456
|
+
const { FiveCompilerWasm } = await import('../wasm/compiler.js');
|
|
457
|
+
const wasmCompiler = new FiveCompilerWasm(logger);
|
|
458
|
+
await wasmCompiler.initialize();
|
|
459
|
+
const result = await wasmCompiler.compile(sourceCode, compilationOptions);
|
|
460
|
+
// Construct fiveFile object if compilation succeeded (since FiveSDK usually provides this)
|
|
461
|
+
if (result.success && !result.fiveFile && result.bytecode) {
|
|
462
|
+
const bytecodeBytes = result.bytecode instanceof Uint8Array
|
|
463
|
+
? result.bytecode
|
|
464
|
+
: new Uint8Array(result.bytecode);
|
|
465
|
+
const bytecodeBase64 = Buffer.from(bytecodeBytes).toString('base64');
|
|
466
|
+
result.fiveFile = {
|
|
467
|
+
bytecode: bytecodeBase64,
|
|
468
|
+
abi: result.abi || { functions: {}, fields: [], version: '1.0' },
|
|
469
|
+
version: '1.0',
|
|
470
|
+
metadata: result.metadata || {}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
// Write metrics report if requested
|
|
474
|
+
if (options.metricsOutput && result.metricsReport?.exported) {
|
|
475
|
+
const metricsPath = isAbsolute(options.metricsOutput)
|
|
476
|
+
? options.metricsOutput
|
|
477
|
+
: resolve(dirname(outputFile), options.metricsOutput);
|
|
478
|
+
await mkdir(dirname(metricsPath), { recursive: true });
|
|
479
|
+
await writeFile(metricsPath, result.metricsReport.exported);
|
|
480
|
+
if (context.options.verbose) {
|
|
481
|
+
logger.info(`Metrics written to ${metricsPath} (${result.metricsReport.format})`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// Write output file if successful
|
|
485
|
+
if (result.success) {
|
|
486
|
+
if (options.debug) {
|
|
487
|
+
console.log('Debug: outputFile =', outputFile);
|
|
488
|
+
console.log('Debug: result.fiveFile exists =', !!result.fiveFile);
|
|
489
|
+
console.log('Debug: endsWith .five =', outputFile.endsWith('.five'));
|
|
490
|
+
}
|
|
491
|
+
if (outputFile.endsWith('.five') && result.fiveFile) {
|
|
492
|
+
// Write .five format (default)
|
|
493
|
+
await writeFile(outputFile, JSON.stringify(result.fiveFile, null, 2));
|
|
494
|
+
}
|
|
495
|
+
else if (result.bytecode) {
|
|
496
|
+
// Write raw bytecode (.bin format)
|
|
497
|
+
await writeFile(outputFile, result.bytecode);
|
|
498
|
+
}
|
|
499
|
+
// Generate separate ABI if requested
|
|
500
|
+
if (options.abi && result.metadata) {
|
|
501
|
+
const abiFile = options.abi;
|
|
502
|
+
await writeFile(abiFile, JSON.stringify(result.metadata, null, 2));
|
|
503
|
+
logger.info(`ABI written to ${abiFile}`);
|
|
504
|
+
}
|
|
505
|
+
// Emit manifest when project config is present
|
|
506
|
+
if (projectContext) {
|
|
507
|
+
const format = outputFile.endsWith('.five') ? 'five' : 'bin';
|
|
508
|
+
const artifactBuffer = format === 'five'
|
|
509
|
+
? Buffer.from(JSON.stringify(result.fiveFile ?? {}))
|
|
510
|
+
: Buffer.from(result.bytecode ?? []);
|
|
511
|
+
const manifest = {
|
|
512
|
+
artifact_path: outputFile,
|
|
513
|
+
abi_path: options.abi,
|
|
514
|
+
compiler_version: 'unknown',
|
|
515
|
+
source_files: (sourceFiles || [inputFile]).map((f) => isAbsolute(f) ? f : resolve(projectContext.rootDir, f)),
|
|
516
|
+
target: projectContext.config.target,
|
|
517
|
+
timestamp: new Date().toISOString(),
|
|
518
|
+
hash: artifactBuffer.length ? computeHash(artifactBuffer) : undefined,
|
|
519
|
+
format,
|
|
520
|
+
entry_point: projectContext.config.entryPoint,
|
|
521
|
+
source_dir: projectContext.config.sourceDir
|
|
522
|
+
};
|
|
523
|
+
const manifestPath = await writeBuildManifest(projectContext.rootDir, manifest);
|
|
524
|
+
if (context.options.verbose) {
|
|
525
|
+
logger.info(`Build manifest written to ${manifestPath}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Auto-generate Types
|
|
529
|
+
if (result.metadata || result.abi) {
|
|
530
|
+
try {
|
|
531
|
+
const abi = result.metadata || result.abi;
|
|
532
|
+
const generator = new TypeGenerator(abi);
|
|
533
|
+
const typeDefs = generator.generate();
|
|
534
|
+
// Generate .d.ts path: replace extension with .d.ts
|
|
535
|
+
// If output is .five or .bin, strip and add .d.ts
|
|
536
|
+
const typeFile = outputFile.replace(/(\.five|\.bin|\.fbin)$/, '') + '.d.ts';
|
|
537
|
+
await writeFile(typeFile, typeDefs);
|
|
538
|
+
if (context.options.verbose)
|
|
539
|
+
logger.info(`Types generated at ${typeFile}`);
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
logger.warn(`Failed to generate types: ${err}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Perform bytecode analysis if requested
|
|
547
|
+
if (options.analyze && result.success && result.bytecode) {
|
|
548
|
+
try {
|
|
549
|
+
const validation = await FiveSDK.validateBytecode(result.bytecode, { debug: true });
|
|
550
|
+
displayBytecodeAnalysis({ validation }, logger);
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
logger.warn(`Failed to analyze bytecode: ${error}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Convert result format for CLI compatibility
|
|
557
|
+
return {
|
|
558
|
+
success: result.success,
|
|
559
|
+
errors: result.errors,
|
|
560
|
+
metrics: {
|
|
561
|
+
sourceSize: sourceCode.length,
|
|
562
|
+
bytecodeSize: result.bytecode?.length || 0
|
|
563
|
+
},
|
|
564
|
+
metricsReport: result.metricsReport,
|
|
565
|
+
formattedErrorsTerminal: result.formattedErrorsTerminal
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Compile a multi-file project (entry + modules) in a single invocation.
|
|
570
|
+
*/
|
|
571
|
+
async function compileMultiProject(inputFiles, options, context, projectContext) {
|
|
572
|
+
const { logger } = context;
|
|
573
|
+
if (!projectContext) {
|
|
574
|
+
throw new Error('Project context is required for multi-file compilation');
|
|
575
|
+
}
|
|
576
|
+
const entryPoint = projectContext.config.entryPoint;
|
|
577
|
+
if (!entryPoint) {
|
|
578
|
+
throw new Error('multi_file_mode is enabled but entry_point is not set in five.toml');
|
|
579
|
+
}
|
|
580
|
+
const absoluteEntry = join(projectContext.rootDir, entryPoint);
|
|
581
|
+
const allFiles = inputFiles.length > 0 ? inputFiles : [absoluteEntry];
|
|
582
|
+
const sources = allFiles.some((f) => resolve(f) === resolve(absoluteEntry))
|
|
583
|
+
? allFiles
|
|
584
|
+
: [absoluteEntry, ...allFiles];
|
|
585
|
+
const mainSource = await readFile(absoluteEntry, 'utf8');
|
|
586
|
+
const modules = await Promise.all(sources
|
|
587
|
+
.filter((f) => resolve(f) !== resolve(absoluteEntry))
|
|
588
|
+
.map(async (file) => ({
|
|
589
|
+
name: basename(file, '.v'),
|
|
590
|
+
source: await readFile(file, 'utf8')
|
|
591
|
+
})));
|
|
592
|
+
const compilationOptions = {
|
|
593
|
+
optimize: parseOptimizationLevel(options.optimize),
|
|
594
|
+
debug: Boolean(options.debug),
|
|
595
|
+
optimizationLevel: 'production',
|
|
596
|
+
includeMetrics: Boolean(options.metricsOutput),
|
|
597
|
+
metricsFormat: options.metricsFormat || 'json',
|
|
598
|
+
errorFormat: options.errorFormat || 'terminal',
|
|
599
|
+
comprehensiveMetrics: Boolean(options.comprehensiveMetrics),
|
|
600
|
+
metricsOutput: options.metricsOutput,
|
|
601
|
+
target: options.target || projectContext.config.target || 'vm',
|
|
602
|
+
flatNamespace: Boolean(options.flatNamespace)
|
|
603
|
+
};
|
|
604
|
+
const outputFile = options.output ||
|
|
605
|
+
getDefaultOutputPath(absoluteEntry, options.target || projectContext?.config.target || 'vm', true, projectContext?.config, projectContext?.rootDir);
|
|
606
|
+
let result;
|
|
607
|
+
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
|
608
|
+
if (context.options.verbose) {
|
|
609
|
+
logger.info(`Compiling multi-file project (${sources.length} files)...`);
|
|
610
|
+
}
|
|
611
|
+
if (isTestEnv) {
|
|
612
|
+
result = await FiveSDK.compileModules(mainSource, modules, compilationOptions);
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
// Use WASM compiler directly for multi-file compilation (like five-frontend-2)
|
|
616
|
+
const { FiveCompilerWasm } = await import('../wasm/compiler.js');
|
|
617
|
+
const wasmCompiler = new FiveCompilerWasm(logger);
|
|
618
|
+
await wasmCompiler.initialize();
|
|
619
|
+
try {
|
|
620
|
+
// Call compileModules which internally uses compile_multi
|
|
621
|
+
result = await wasmCompiler.compileModules(mainSource, modules, compilationOptions, isAbsolute(entryPoint) ? basename(entryPoint) : entryPoint);
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
// If multi-file fails, fall back to FiveSDK
|
|
625
|
+
if (context.options.verbose) {
|
|
626
|
+
logger.warn(`Multi-file compilation failed: ${err.message}, falling back to SDK...`);
|
|
627
|
+
}
|
|
628
|
+
result = await FiveSDK.compileModules(mainSource, modules, compilationOptions);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (context.options.verbose) {
|
|
632
|
+
logger.debug(`Compilation result: success=${result.success}, has_bytecode=${!!result.bytecode}, has_abi=${!!result.abi}, errors=${result.errors ? result.errors.length : 0}`);
|
|
633
|
+
}
|
|
634
|
+
await mkdir(dirname(outputFile), { recursive: true });
|
|
635
|
+
if (result.success) {
|
|
636
|
+
// Construct fiveFile object from compilation result if needed
|
|
637
|
+
if (!result.fiveFile && result.bytecode) {
|
|
638
|
+
const bytecodeBytes = result.bytecode instanceof Uint8Array
|
|
639
|
+
? result.bytecode
|
|
640
|
+
: new Uint8Array(result.bytecode);
|
|
641
|
+
const bytecodeBase64 = Buffer.from(bytecodeBytes).toString('base64');
|
|
642
|
+
result.fiveFile = {
|
|
643
|
+
bytecode: bytecodeBase64,
|
|
644
|
+
abi: result.abi || { functions: {}, fields: [], version: '1.0' },
|
|
645
|
+
version: '1.0',
|
|
646
|
+
metadata: result.metadata || {}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
if (outputFile.endsWith('.five') && result.fiveFile) {
|
|
650
|
+
await writeFile(outputFile, JSON.stringify(result.fiveFile, null, 2));
|
|
651
|
+
}
|
|
652
|
+
else if (result.bytecode) {
|
|
653
|
+
const bytecodeBytes = result.bytecode instanceof Uint8Array
|
|
654
|
+
? result.bytecode
|
|
655
|
+
: new Uint8Array(result.bytecode);
|
|
656
|
+
await writeFile(outputFile, Buffer.from(bytecodeBytes));
|
|
657
|
+
}
|
|
658
|
+
if (options.abi && result.metadata) {
|
|
659
|
+
await writeFile(options.abi, JSON.stringify(result.metadata, null, 2));
|
|
660
|
+
logger.info(`ABI written to ${options.abi}`);
|
|
661
|
+
}
|
|
662
|
+
if (options.metricsOutput && result.metricsReport?.exported) {
|
|
663
|
+
const metricsPath = isAbsolute(options.metricsOutput)
|
|
664
|
+
? options.metricsOutput
|
|
665
|
+
: resolve(dirname(outputFile), options.metricsOutput);
|
|
666
|
+
await mkdir(dirname(metricsPath), { recursive: true });
|
|
667
|
+
await writeFile(metricsPath, result.metricsReport.exported);
|
|
668
|
+
if (context.options.verbose) {
|
|
669
|
+
logger.info(`Metrics written to ${metricsPath} (${result.metricsReport.format})`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Auto-generate Types
|
|
673
|
+
if (result.metadata || result.abi) {
|
|
674
|
+
try {
|
|
675
|
+
const abi = result.metadata || result.abi;
|
|
676
|
+
const generator = new TypeGenerator(abi);
|
|
677
|
+
const typeDefs = generator.generate();
|
|
678
|
+
// Generate .d.ts path: replace extension with .d.ts
|
|
679
|
+
// If output is .five or .bin, strip and add .d.ts
|
|
680
|
+
const typeFile = outputFile.replace(/(\.five|\.bin|\.fbin)$/, '') + '.d.ts';
|
|
681
|
+
await writeFile(typeFile, typeDefs);
|
|
682
|
+
if (context.options.verbose)
|
|
683
|
+
logger.info(`Types generated at ${typeFile}`);
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
logger.warn(`Failed to generate types: ${err}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const format = outputFile.endsWith('.five') ? 'five' : 'bin';
|
|
690
|
+
const artifactBuffer = format === 'five'
|
|
691
|
+
? Buffer.from(JSON.stringify(result.fiveFile ?? {}))
|
|
692
|
+
: Buffer.from(result.bytecode ?? []);
|
|
693
|
+
const manifest = {
|
|
694
|
+
artifact_path: outputFile,
|
|
695
|
+
abi_path: options.abi,
|
|
696
|
+
compiler_version: 'unknown',
|
|
697
|
+
source_files: sources.map((f) => (isAbsolute(f) ? f : resolve(projectContext.rootDir, f))),
|
|
698
|
+
target: projectContext.config.target,
|
|
699
|
+
timestamp: new Date().toISOString(),
|
|
700
|
+
hash: artifactBuffer.length ? computeHash(artifactBuffer) : undefined,
|
|
701
|
+
format,
|
|
702
|
+
entry_point: projectContext.config.entryPoint,
|
|
703
|
+
source_dir: projectContext.config.sourceDir
|
|
704
|
+
};
|
|
705
|
+
const manifestPath = await writeBuildManifest(projectContext.rootDir, manifest);
|
|
706
|
+
if (context.options.verbose) {
|
|
707
|
+
logger.info(`Build manifest written to ${manifestPath}`);
|
|
708
|
+
}
|
|
709
|
+
console.log(uiSuccess(`build (${modules.length + 1} files, ${artifactBuffer.length} bytes)`));
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
console.log(uiError('build failed'));
|
|
713
|
+
if (context.options.verbose) {
|
|
714
|
+
logger.debug(`formattedErrorsTerminal prop: ${result.formattedErrorsTerminal ? result.formattedErrorsTerminal.substring(0, 200) : 'EMPTY/UNDEFINED'}`);
|
|
715
|
+
}
|
|
716
|
+
if (result.formattedErrorsTerminal) {
|
|
717
|
+
console.log(result.formattedErrorsTerminal);
|
|
718
|
+
}
|
|
719
|
+
else if (result.errors) {
|
|
720
|
+
result.errors.forEach((err) => {
|
|
721
|
+
const errorMsg = typeof err === 'string' ? err : (err.message || err.code || JSON.stringify(err) || 'Unknown error');
|
|
722
|
+
console.error(` Error: ${errorMsg}`);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (context.options.verbose && result.errors) {
|
|
726
|
+
logger.debug(`Full compilation errors: ${JSON.stringify(result.errors, null, 2)}`);
|
|
727
|
+
}
|
|
728
|
+
throw new Error('Multi-file build failed');
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Watch files for changes and recompile
|
|
733
|
+
*/
|
|
734
|
+
async function watchAndCompile(inputFiles, options, context, projectContext) {
|
|
735
|
+
const { logger } = context;
|
|
736
|
+
// Import chokidar dynamically for file watching
|
|
737
|
+
const chokidar = await import('chokidar');
|
|
738
|
+
logger.info('Watching for file changes...');
|
|
739
|
+
// Initial compilation
|
|
740
|
+
await compileFiles(inputFiles, { ...options, watch: false }, context, projectContext);
|
|
741
|
+
// Watch for changes
|
|
742
|
+
const watcher = chokidar.watch(inputFiles, {
|
|
743
|
+
persistent: true,
|
|
744
|
+
ignoreInitial: true
|
|
745
|
+
});
|
|
746
|
+
watcher.on('change', async (filePath) => {
|
|
747
|
+
logger.info(`File changed: ${filePath}`);
|
|
748
|
+
try {
|
|
749
|
+
await compileSingleFile(filePath, options, context);
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
logger.error(`Recompilation failed: ${error}`);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
// Handle graceful shutdown
|
|
756
|
+
process.on('SIGINT', () => {
|
|
757
|
+
logger.info('Stopping file watcher...');
|
|
758
|
+
watcher.close();
|
|
759
|
+
process.exit(0);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Get default output path based on input file and target
|
|
764
|
+
*/
|
|
765
|
+
function getDefaultOutputPath(inputFile, target, useFiveFormat = true, projectConfig, projectRoot) {
|
|
766
|
+
const dir = projectConfig
|
|
767
|
+
? join(projectRoot ?? process.cwd(), projectConfig.buildDir)
|
|
768
|
+
: dirname(inputFile);
|
|
769
|
+
const baseName = projectConfig?.outputArtifactName || projectConfig?.name || basename(inputFile, '.v');
|
|
770
|
+
// Use .five format by default, .bin for legacy
|
|
771
|
+
const resolvedTarget = target || projectConfig?.target || 'vm';
|
|
772
|
+
const extensions = {
|
|
773
|
+
vm: useFiveFormat ? '.five' : '.bin',
|
|
774
|
+
solana: useFiveFormat ? '.five' : '.so',
|
|
775
|
+
debug: useFiveFormat ? '.five' : '.debug.bin',
|
|
776
|
+
test: useFiveFormat ? '.five' : '.test.bin'
|
|
777
|
+
};
|
|
778
|
+
const ext = extensions[resolvedTarget] || (useFiveFormat ? '.five' : '.bin');
|
|
779
|
+
return join(dir, baseName + ext);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Parse optimization level from command line option
|
|
783
|
+
*/
|
|
784
|
+
function parseOptimizationLevel(optimize) {
|
|
785
|
+
if (optimize === false || optimize === 'false' || optimize === '0') {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Display bytecode analysis using real analyzer results
|
|
792
|
+
*/
|
|
793
|
+
function displayBytecodeAnalysis(analysis, logger) {
|
|
794
|
+
console.log('\n' + section('Bytecode Analysis'));
|
|
795
|
+
if (analysis.summary) {
|
|
796
|
+
console.log(` Total size: ${analysis.summary.total_size} bytes`);
|
|
797
|
+
console.log(` Instructions: ${analysis.summary.total_instructions}`);
|
|
798
|
+
console.log(` Compute units: ${analysis.summary.total_compute_units}`);
|
|
799
|
+
console.log(` Functions: ${analysis.summary.has_function_calls ? 'Yes' : 'No'}`);
|
|
800
|
+
console.log(` Jumps: ${analysis.summary.has_jumps ? 'Yes' : 'No'}`);
|
|
801
|
+
}
|
|
802
|
+
if (analysis.stack_analysis) {
|
|
803
|
+
console.log(` Max stack depth: ${analysis.stack_analysis.max_stack_depth}`);
|
|
804
|
+
console.log(` Stack consistency: ${analysis.stack_analysis.is_consistent ? 'Valid' : 'Invalid'}`);
|
|
805
|
+
}
|
|
806
|
+
if (analysis.control_flow && analysis.control_flow.basic_blocks) {
|
|
807
|
+
console.log(` Basic blocks: ${analysis.control_flow.basic_blocks.length}`);
|
|
808
|
+
}
|
|
809
|
+
// Show top 5 most frequent instructions
|
|
810
|
+
if (analysis.instructions && analysis.instructions.length > 0) {
|
|
811
|
+
console.log('\n' + section('Instruction Breakdown'));
|
|
812
|
+
const instructionCounts = new Map();
|
|
813
|
+
for (const inst of analysis.instructions) {
|
|
814
|
+
const count = instructionCounts.get(inst.name) || 0;
|
|
815
|
+
instructionCounts.set(inst.name, count + 1);
|
|
816
|
+
}
|
|
817
|
+
const sorted = Array.from(instructionCounts.entries())
|
|
818
|
+
.sort((a, b) => b[1] - a[1])
|
|
819
|
+
.slice(0, 5);
|
|
820
|
+
for (const [name, count] of sorted) {
|
|
821
|
+
console.log(` ${name}: ${count} times`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Display opcode usage analysis
|
|
827
|
+
*/
|
|
828
|
+
function displayOpcodeAnalysis(opcodeUsage, opcodeAnalysis, logger) {
|
|
829
|
+
console.log('\n' + section('Opcode Usage Analysis'));
|
|
830
|
+
if (opcodeUsage) {
|
|
831
|
+
console.log(` Total opcodes generated: ${opcodeUsage.total_opcodes}`);
|
|
832
|
+
console.log(` Unique opcodes used: ${opcodeUsage.unique_opcodes}`);
|
|
833
|
+
if (opcodeUsage.top_opcodes && opcodeUsage.top_opcodes.length > 0) {
|
|
834
|
+
console.log('\n' + section('Top Used Opcodes'));
|
|
835
|
+
opcodeUsage.top_opcodes.slice(0, 5).forEach(([opcode, count], index) => {
|
|
836
|
+
console.log(` ${index + 1}. ${opcode}: ${count} times`);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
if (opcodeUsage.category_distribution) {
|
|
840
|
+
console.log('\n' + section('Opcode Categories'));
|
|
841
|
+
Object.entries(opcodeUsage.category_distribution).forEach(([category, count]) => {
|
|
842
|
+
console.log(` ${category}: ${count} opcodes`);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (opcodeAnalysis && opcodeAnalysis.summary) {
|
|
847
|
+
console.log('\n' + section('Comprehensive Opcode Analysis'));
|
|
848
|
+
console.log(` Available opcodes in Five VM: ${opcodeAnalysis.summary.total_opcodes_available}`);
|
|
849
|
+
console.log(` Opcodes used by this script: ${opcodeAnalysis.summary.opcodes_used}`);
|
|
850
|
+
console.log(` Opcodes unused: ${opcodeAnalysis.summary.opcodes_unused}`);
|
|
851
|
+
console.log(` Usage percentage: ${opcodeAnalysis.summary.usage_percentage.toFixed(1)}%`);
|
|
852
|
+
if (opcodeAnalysis.used_opcodes && opcodeAnalysis.used_opcodes.length > 0) {
|
|
853
|
+
console.log('\n' + section('Used Opcodes'));
|
|
854
|
+
opcodeAnalysis.used_opcodes.slice(0, 10).forEach((opcode) => {
|
|
855
|
+
console.log(` - ${opcode.name} (used ${opcode.usage_count} times)`);
|
|
856
|
+
});
|
|
857
|
+
if (opcodeAnalysis.used_opcodes.length > 10) {
|
|
858
|
+
console.log(` ... and ${opcodeAnalysis.used_opcodes.length - 10} more`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (opcodeAnalysis.unused_opcodes && opcodeAnalysis.unused_opcodes.length > 0) {
|
|
862
|
+
console.log('\n' + section('Sample Unused Opcodes'));
|
|
863
|
+
opcodeAnalysis.unused_opcodes.slice(0, 8).forEach((opcode) => {
|
|
864
|
+
console.log(` - ${opcode.name}`);
|
|
865
|
+
});
|
|
866
|
+
if (opcodeAnalysis.unused_opcodes.length > 8) {
|
|
867
|
+
console.log(` ... and ${opcodeAnalysis.unused_opcodes.length - 8} more unused opcodes`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
//# sourceMappingURL=compile.js.map
|