@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.
Files changed (139) hide show
  1. package/README.md +226 -0
  2. package/dist/assets/vm/five_vm_wasm.d.ts +762 -0
  3. package/dist/assets/vm/five_vm_wasm.js +3754 -0
  4. package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
  5. package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +247 -0
  6. package/dist/assets/vm/package.json +11 -0
  7. package/dist/cli.d.ts +47 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +343 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/commands/analyze.d.ts +3 -0
  12. package/dist/commands/analyze.d.ts.map +1 -0
  13. package/dist/commands/analyze.js +435 -0
  14. package/dist/commands/analyze.js.map +1 -0
  15. package/dist/commands/build.d.ts +3 -0
  16. package/dist/commands/build.d.ts.map +1 -0
  17. package/dist/commands/build.js +66 -0
  18. package/dist/commands/build.js.map +1 -0
  19. package/dist/commands/compile.d.ts +3 -0
  20. package/dist/commands/compile.d.ts.map +1 -0
  21. package/dist/commands/compile.js +872 -0
  22. package/dist/commands/compile.js.map +1 -0
  23. package/dist/commands/config.d.ts +3 -0
  24. package/dist/commands/config.d.ts.map +1 -0
  25. package/dist/commands/config.js +431 -0
  26. package/dist/commands/config.js.map +1 -0
  27. package/dist/commands/deploy-and-execute.d.ts +3 -0
  28. package/dist/commands/deploy-and-execute.d.ts.map +1 -0
  29. package/dist/commands/deploy-and-execute.js +317 -0
  30. package/dist/commands/deploy-and-execute.js.map +1 -0
  31. package/dist/commands/deploy.d.ts +21 -0
  32. package/dist/commands/deploy.d.ts.map +1 -0
  33. package/dist/commands/deploy.js +806 -0
  34. package/dist/commands/deploy.js.map +1 -0
  35. package/dist/commands/donate.d.ts +4 -0
  36. package/dist/commands/donate.d.ts.map +1 -0
  37. package/dist/commands/donate.js +104 -0
  38. package/dist/commands/donate.js.map +1 -0
  39. package/dist/commands/execute.d.ts +6 -0
  40. package/dist/commands/execute.d.ts.map +1 -0
  41. package/dist/commands/execute.js +749 -0
  42. package/dist/commands/execute.js.map +1 -0
  43. package/dist/commands/fmt.d.ts +3 -0
  44. package/dist/commands/fmt.d.ts.map +1 -0
  45. package/dist/commands/fmt.js +327 -0
  46. package/dist/commands/fmt.js.map +1 -0
  47. package/dist/commands/help.d.ts +6 -0
  48. package/dist/commands/help.d.ts.map +1 -0
  49. package/dist/commands/help.js +224 -0
  50. package/dist/commands/help.js.map +1 -0
  51. package/dist/commands/index.d.ts +45 -0
  52. package/dist/commands/index.d.ts.map +1 -0
  53. package/dist/commands/index.js +119 -0
  54. package/dist/commands/index.js.map +1 -0
  55. package/dist/commands/init.d.ts +3 -0
  56. package/dist/commands/init.d.ts.map +1 -0
  57. package/dist/commands/init.js +887 -0
  58. package/dist/commands/init.js.map +1 -0
  59. package/dist/commands/local.d.ts +3 -0
  60. package/dist/commands/local.d.ts.map +1 -0
  61. package/dist/commands/local.js +703 -0
  62. package/dist/commands/local.js.map +1 -0
  63. package/dist/commands/namespace.d.ts +3 -0
  64. package/dist/commands/namespace.d.ts.map +1 -0
  65. package/dist/commands/namespace.js +328 -0
  66. package/dist/commands/namespace.js.map +1 -0
  67. package/dist/commands/template.d.ts +4 -0
  68. package/dist/commands/template.d.ts.map +1 -0
  69. package/dist/commands/template.js +486 -0
  70. package/dist/commands/template.js.map +1 -0
  71. package/dist/commands/test.d.ts +6 -0
  72. package/dist/commands/test.d.ts.map +1 -0
  73. package/dist/commands/test.js +890 -0
  74. package/dist/commands/test.js.map +1 -0
  75. package/dist/commands/version.d.ts +6 -0
  76. package/dist/commands/version.d.ts.map +1 -0
  77. package/dist/commands/version.js +339 -0
  78. package/dist/commands/version.js.map +1 -0
  79. package/dist/config/ConfigManager.d.ts +69 -0
  80. package/dist/config/ConfigManager.d.ts.map +1 -0
  81. package/dist/config/ConfigManager.js +261 -0
  82. package/dist/config/ConfigManager.js.map +1 -0
  83. package/dist/config/index.d.ts +10 -0
  84. package/dist/config/index.d.ts.map +1 -0
  85. package/dist/config/index.js +21 -0
  86. package/dist/config/index.js.map +1 -0
  87. package/dist/config/types.d.ts +35 -0
  88. package/dist/config/types.d.ts.map +1 -0
  89. package/dist/config/types.js +105 -0
  90. package/dist/config/types.js.map +1 -0
  91. package/dist/index.d.ts +3 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +29 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/project/ProjectLoader.d.ts +12 -0
  96. package/dist/project/ProjectLoader.d.ts.map +1 -0
  97. package/dist/project/ProjectLoader.js +115 -0
  98. package/dist/project/ProjectLoader.js.map +1 -0
  99. package/dist/types.d.ts +334 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +2 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/utils/AccountFixtureGenerator.d.ts +48 -0
  104. package/dist/utils/AccountFixtureGenerator.d.ts.map +1 -0
  105. package/dist/utils/AccountFixtureGenerator.js +265 -0
  106. package/dist/utils/AccountFixtureGenerator.js.map +1 -0
  107. package/dist/utils/FiveFileManager.d.ts +96 -0
  108. package/dist/utils/FiveFileManager.d.ts.map +1 -0
  109. package/dist/utils/FiveFileManager.js +329 -0
  110. package/dist/utils/FiveFileManager.js.map +1 -0
  111. package/dist/utils/ascii-art.d.ts +72 -0
  112. package/dist/utils/ascii-art.d.ts.map +1 -0
  113. package/dist/utils/ascii-art.js +314 -0
  114. package/dist/utils/ascii-art.js.map +1 -0
  115. package/dist/utils/cli-ui.d.ts +39 -0
  116. package/dist/utils/cli-ui.d.ts.map +1 -0
  117. package/dist/utils/cli-ui.js +75 -0
  118. package/dist/utils/cli-ui.js.map +1 -0
  119. package/dist/utils/fileUtils.d.ts +25 -0
  120. package/dist/utils/fileUtils.d.ts.map +1 -0
  121. package/dist/utils/fileUtils.js +50 -0
  122. package/dist/utils/fileUtils.js.map +1 -0
  123. package/dist/utils/logger.d.ts +53 -0
  124. package/dist/utils/logger.d.ts.map +1 -0
  125. package/dist/utils/logger.js +287 -0
  126. package/dist/utils/logger.js.map +1 -0
  127. package/dist/wasm/compiler.d.ts +101 -0
  128. package/dist/wasm/compiler.d.ts.map +1 -0
  129. package/dist/wasm/compiler.js +906 -0
  130. package/dist/wasm/compiler.js.map +1 -0
  131. package/dist/wasm/loader.d.ts +2 -0
  132. package/dist/wasm/loader.d.ts.map +1 -0
  133. package/dist/wasm/loader.js +90 -0
  134. package/dist/wasm/loader.js.map +1 -0
  135. package/dist/wasm/vm.d.ts +32 -0
  136. package/dist/wasm/vm.d.ts.map +1 -0
  137. package/dist/wasm/vm.js +440 -0
  138. package/dist/wasm/vm.js.map +1 -0
  139. 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