@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,703 @@
|
|
|
1
|
+
// Local WASM execution commands.
|
|
2
|
+
import { readFile, readdir, stat } from 'fs/promises';
|
|
3
|
+
import { join, extname, basename } from 'path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { FiveSDK } from '@5ive-tech/sdk';
|
|
6
|
+
import { section, success as uiSuccess, error as uiError } from '../utils/cli-ui.js';
|
|
7
|
+
// Parent for local subcommands.
|
|
8
|
+
export const localCommand = {
|
|
9
|
+
name: 'local',
|
|
10
|
+
description: 'Local WASM execution',
|
|
11
|
+
aliases: ['l'],
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
flags: '-f, --function <call>',
|
|
15
|
+
description: 'Function call: add(5, 3) or function name/index'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
flags: '-p, --params <values...>',
|
|
19
|
+
description: 'Function parameters (space-separated or JSON)'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
flags: '--debug',
|
|
23
|
+
description: 'Enable debug output'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
flags: '--trace',
|
|
27
|
+
description: 'Enable execution trace'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
flags: '--max-cu <units>',
|
|
31
|
+
description: 'Max compute units (default: 1000000)'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
flags: '--max-compute-units <units>',
|
|
35
|
+
description: 'Alias for --max-cu'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
flags: '--format <format>',
|
|
39
|
+
description: 'Output format: text, json (default: text)'
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
arguments: [
|
|
43
|
+
{
|
|
44
|
+
name: 'subcommand',
|
|
45
|
+
description: 'Local subcommand (execute, test, compile)',
|
|
46
|
+
required: true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'args',
|
|
50
|
+
description: 'Arguments for the subcommand',
|
|
51
|
+
required: false,
|
|
52
|
+
variadic: true
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
examples: [
|
|
56
|
+
{
|
|
57
|
+
command: 'five local execute script.bin',
|
|
58
|
+
description: 'Execute script locally using WASM VM'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
command: 'five local execute script.five add 5 3',
|
|
62
|
+
description: 'Execute add function with parameters 5 and 3'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
command: 'five local execute script.five test',
|
|
66
|
+
description: 'Execute test function with no parameters'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
command: 'five local execute script.five 2',
|
|
70
|
+
description: 'Execute function at index 2'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
command: 'five local test',
|
|
74
|
+
description: 'Run local test suite with WASM VM'
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
handler: async (args, options, context) => {
|
|
78
|
+
const { logger } = context;
|
|
79
|
+
const flatArgs = args ?? [];
|
|
80
|
+
if (flatArgs.length === 0) {
|
|
81
|
+
showLocalHelp(logger);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const [subcommand, ...rawSubArgs] = flatArgs;
|
|
85
|
+
const subArgs = rawSubArgs.flatMap((arg) => {
|
|
86
|
+
if (Array.isArray(arg)) {
|
|
87
|
+
return arg.filter((value) => typeof value === 'string');
|
|
88
|
+
}
|
|
89
|
+
return typeof arg === 'string' ? [arg] : [];
|
|
90
|
+
});
|
|
91
|
+
// Local WASM execution mode
|
|
92
|
+
if (context.options.verbose) {
|
|
93
|
+
logger.info('Local WASM VM');
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
switch (subcommand) {
|
|
97
|
+
case 'execute':
|
|
98
|
+
case 'exec':
|
|
99
|
+
case 'run':
|
|
100
|
+
await executeLocalSubcommand(subArgs, options, context);
|
|
101
|
+
break;
|
|
102
|
+
case 'test':
|
|
103
|
+
case 't':
|
|
104
|
+
await testLocalSubcommand(subArgs, options, context);
|
|
105
|
+
break;
|
|
106
|
+
case 'compile':
|
|
107
|
+
case 'c':
|
|
108
|
+
await compileLocalSubcommand(subArgs, options, context);
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
logger.error(`Unknown local subcommand: ${subcommand}`);
|
|
112
|
+
showLocalHelp(logger);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
logger.error(`Local ${subcommand} failed:`, error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Local execute subcommand - execute bytecode in local WASM VM
|
|
124
|
+
*/
|
|
125
|
+
async function executeLocalSubcommand(args, options, context) {
|
|
126
|
+
const { logger } = context;
|
|
127
|
+
if (options.debug) {
|
|
128
|
+
logger.debug(`local execute args: ${JSON.stringify(args)}`);
|
|
129
|
+
}
|
|
130
|
+
if (args.length === 0) {
|
|
131
|
+
console.log(uiError('No file specified for execution'));
|
|
132
|
+
console.log(section('Usage'));
|
|
133
|
+
console.log(' five local execute <file> [function] [params...]');
|
|
134
|
+
console.log(section('Arguments'));
|
|
135
|
+
console.log(' <file> .five/.bin/.v file to execute');
|
|
136
|
+
console.log(' [function] Function name or index');
|
|
137
|
+
console.log(' [params...] Function parameters (space-separated)');
|
|
138
|
+
console.log(section('Options'));
|
|
139
|
+
console.log(' --debug Enable debug output');
|
|
140
|
+
console.log(' --trace Enable execution trace');
|
|
141
|
+
console.log(' --max-cu <n> Max compute units (default: 1000000)');
|
|
142
|
+
console.log(' --format <f> Output format: text, json (default: text)');
|
|
143
|
+
console.log(section('Examples'));
|
|
144
|
+
console.log(' five local execute script.five add 5 3');
|
|
145
|
+
console.log(' five local execute script.five test');
|
|
146
|
+
console.log(' five local execute script.five 2');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const inputFile = args[0];
|
|
150
|
+
const rawFunctionName = args.length > 1 ? args[1] : undefined;
|
|
151
|
+
// Convert numeric strings to numbers for function index
|
|
152
|
+
let functionName = rawFunctionName;
|
|
153
|
+
if (rawFunctionName && /^\d+$/.test(rawFunctionName)) {
|
|
154
|
+
functionName = parseInt(rawFunctionName, 10);
|
|
155
|
+
}
|
|
156
|
+
const functionParams = args.length > 2 ? args.slice(2) : [];
|
|
157
|
+
if (options.debug) {
|
|
158
|
+
logger.debug(`local execute parsed: ${JSON.stringify({ inputFile, functionName, functionParams })}`);
|
|
159
|
+
}
|
|
160
|
+
// Initialize for local execution
|
|
161
|
+
if (context.options.verbose) {
|
|
162
|
+
const spinner = ora('Preparing local execution...').start();
|
|
163
|
+
spinner.text = 'Loading Five SDK...';
|
|
164
|
+
spinner.succeed('Five SDK ready for local execution');
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
// Use space-separated syntax: file function param1 param2 ...
|
|
168
|
+
let functionRef = functionName || options.function || options.functionIndex || 0;
|
|
169
|
+
const parameters = functionParams.length > 0 ?
|
|
170
|
+
(Array.isArray(functionParams) ? functionParams.map(parseValue) : [parseValue(functionParams)]) :
|
|
171
|
+
parseParameters(options.params);
|
|
172
|
+
const debug = Boolean(options.debug || options.trace);
|
|
173
|
+
const trace = Boolean(options.trace);
|
|
174
|
+
const maxCU = options.maxCu || options.maxComputeUnits || 1000000;
|
|
175
|
+
const format = options.format || 'text';
|
|
176
|
+
// Keep function reference as-is - let the compiler handle function name resolution
|
|
177
|
+
// The Five compiler can resolve function names to indices properly
|
|
178
|
+
// Debug parameter parsing
|
|
179
|
+
if (debug) {
|
|
180
|
+
logger.debug(`local function: ${functionRef}`);
|
|
181
|
+
logger.debug(`local params: ${JSON.stringify(parameters)}`);
|
|
182
|
+
}
|
|
183
|
+
let result;
|
|
184
|
+
if (extname(inputFile) === '.five') {
|
|
185
|
+
// Handle .five files with embedded ABI
|
|
186
|
+
logger.info(`Executing Five file: ${inputFile}`);
|
|
187
|
+
const fileContent = await readFile(inputFile, 'utf8');
|
|
188
|
+
const { bytecode, abi } = await FiveSDK.loadFiveFile(fileContent);
|
|
189
|
+
// FUNCTION RESOLUTION FIX: Systematic approach for .five files
|
|
190
|
+
let resolvedFunctionRef = functionRef;
|
|
191
|
+
if (functionRef === undefined) {
|
|
192
|
+
// SYSTEMATIC APPROACH: Public functions always have the lowest indices (0, 1, 2...)
|
|
193
|
+
// So we always default to function index 0, which is guaranteed to be public if any functions exist
|
|
194
|
+
resolvedFunctionRef = 0;
|
|
195
|
+
if (debug && abi && abi.functions) {
|
|
196
|
+
let functionName = 'function_0';
|
|
197
|
+
if (Array.isArray(abi.functions)) {
|
|
198
|
+
// FIVEABI format: find function with index 0
|
|
199
|
+
const func0 = abi.functions.find((f) => f.index === 0);
|
|
200
|
+
if (func0 && func0.name)
|
|
201
|
+
functionName = func0.name;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// SimpleABI format: find function with index 0
|
|
205
|
+
const func0Entry = Object.entries(abi.functions).find(([_, f]) => f.index === 0);
|
|
206
|
+
if (func0Entry)
|
|
207
|
+
functionName = func0Entry[0];
|
|
208
|
+
}
|
|
209
|
+
logger.debug(`auto-detected public function: ${functionName} (index 0)`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
result = await FiveSDK.executeLocally(bytecode, resolvedFunctionRef, parameters, {
|
|
213
|
+
debug,
|
|
214
|
+
trace,
|
|
215
|
+
computeUnitLimit: maxCU,
|
|
216
|
+
abi // Pass ABI for function name resolution
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
else if (extname(inputFile) === '.v') {
|
|
220
|
+
// Compile and execute Five source file
|
|
221
|
+
logger.info(`Compiling and executing Five source: ${inputFile}`);
|
|
222
|
+
const sourceCode = await readFile(inputFile, 'utf8');
|
|
223
|
+
result = await FiveSDK.execute(sourceCode, functionRef, parameters, {
|
|
224
|
+
debug,
|
|
225
|
+
trace,
|
|
226
|
+
optimize: true,
|
|
227
|
+
computeUnitLimit: maxCU
|
|
228
|
+
});
|
|
229
|
+
// Show compilation info
|
|
230
|
+
if ('compilation' in result && result.compilation) {
|
|
231
|
+
console.log(`Compilation: ${result.compilation.success ? 'OK' : 'FAIL'}`);
|
|
232
|
+
if ('bytecodeSize' in result && result.bytecodeSize) {
|
|
233
|
+
console.log(`Bytecode size: ${result.bytecodeSize} bytes`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Execute existing bytecode file
|
|
239
|
+
logger.info(`Executing bytecode: ${inputFile}`);
|
|
240
|
+
const bytecode = await readFile(inputFile);
|
|
241
|
+
result = await FiveSDK.executeLocally(new Uint8Array(bytecode), functionRef, parameters, {
|
|
242
|
+
debug,
|
|
243
|
+
trace,
|
|
244
|
+
computeUnitLimit: maxCU
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// Validate execution result structure
|
|
248
|
+
if (typeof result !== 'object' || result === null) {
|
|
249
|
+
logger.error('Invalid execution result from SDK');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
if (typeof result.success !== 'boolean') {
|
|
253
|
+
logger.error('Execution result missing success field');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
// Display results
|
|
257
|
+
displayLocalExecutionResult(result, { format, trace, debug }, logger);
|
|
258
|
+
// CRITICAL FIX: Check execution success and exit with proper code
|
|
259
|
+
if (!result.success) {
|
|
260
|
+
if (context.options.verbose) {
|
|
261
|
+
logger.error(`Execution failed: ${result.error || 'Unknown error'}`);
|
|
262
|
+
}
|
|
263
|
+
process.exit(1); // EXIT WITH FAILURE CODE
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
if (context.options.verbose) {
|
|
268
|
+
ora().fail('Local execution failed');
|
|
269
|
+
}
|
|
270
|
+
process.exit(1); // Also ensure exceptions exit with failure
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Local test subcommand - run test suite locally
|
|
275
|
+
*/
|
|
276
|
+
async function testLocalSubcommand(args, options, context) {
|
|
277
|
+
const { logger } = context;
|
|
278
|
+
const testPath = args[0] || './tests';
|
|
279
|
+
const pattern = options.pattern || '**/*.test.json';
|
|
280
|
+
const filter = options.filter;
|
|
281
|
+
const verbose = options.verbose || options.debug;
|
|
282
|
+
const format = options.format || 'text';
|
|
283
|
+
logger.info('Running local test suite with WASM VM');
|
|
284
|
+
logger.info(`Test path: ${testPath}`);
|
|
285
|
+
logger.info(`Pattern: ${pattern}`);
|
|
286
|
+
const spinner = ora('Discovering test files...').start();
|
|
287
|
+
try {
|
|
288
|
+
// Discover test files
|
|
289
|
+
const testFiles = await discoverTestFiles(testPath, pattern);
|
|
290
|
+
if (testFiles.length === 0) {
|
|
291
|
+
spinner.warn('No test files found');
|
|
292
|
+
logger.warn(`No test files matching pattern "${pattern}" found in ${testPath}`);
|
|
293
|
+
logger.info('Expected test file format: JSON files with .test.json extension');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
spinner.succeed(`Found ${testFiles.length} test file(s)`);
|
|
297
|
+
// Run tests
|
|
298
|
+
const results = await runLocalTests(testFiles, {
|
|
299
|
+
filter,
|
|
300
|
+
verbose,
|
|
301
|
+
maxCU: options.maxCu || 1000000,
|
|
302
|
+
timeout: options.timeout || 30000
|
|
303
|
+
}, logger);
|
|
304
|
+
// Display results
|
|
305
|
+
displayTestResults(results, { format, verbose }, logger);
|
|
306
|
+
// Exit with error if any tests failed
|
|
307
|
+
const failed = results.some(r => !r.passed);
|
|
308
|
+
if (failed) {
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
spinner.fail('Test discovery failed');
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Local compile subcommand - alias for main compile command
|
|
319
|
+
*/
|
|
320
|
+
async function compileLocalSubcommand(args, options, context) {
|
|
321
|
+
const { logger } = context;
|
|
322
|
+
if (args.length === 0) {
|
|
323
|
+
logger.error('No file specified for compilation');
|
|
324
|
+
logger.info('Usage: five local compile <file> [options]');
|
|
325
|
+
logger.info('This is an alias for: five compile <file>');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
logger.info('Compiling locally (same as five compile)');
|
|
329
|
+
// Import and delegate to compile command
|
|
330
|
+
const { compileCommand } = await import('./compile.js');
|
|
331
|
+
await compileCommand.handler(args, options, context);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Show local command help
|
|
335
|
+
*/
|
|
336
|
+
function showLocalHelp(logger) {
|
|
337
|
+
logger.info('Local Five VM Commands (WASM execution, no config needed):');
|
|
338
|
+
logger.info('');
|
|
339
|
+
logger.info('Subcommands:');
|
|
340
|
+
logger.info(' execute <file> Execute bytecode or source file locally');
|
|
341
|
+
logger.info(' test [pattern] Run local test suite');
|
|
342
|
+
logger.info(' compile <file> Compile source file (alias for main compile)');
|
|
343
|
+
logger.info('');
|
|
344
|
+
logger.info('Examples:');
|
|
345
|
+
logger.info(' five local execute script.bin');
|
|
346
|
+
logger.info(' five local execute script.v --function 1 --parameters "[10, 5]"');
|
|
347
|
+
logger.info(' five local test --pattern "*.test.json" --verbose');
|
|
348
|
+
logger.info(' five local compile script.v --optimize');
|
|
349
|
+
logger.info('');
|
|
350
|
+
logger.info('Features:');
|
|
351
|
+
logger.info(' • Always uses WASM VM (no network required)');
|
|
352
|
+
logger.info(' • No configuration dependencies');
|
|
353
|
+
logger.info(' • Fast development iteration');
|
|
354
|
+
logger.info(' • Compute unit tracking');
|
|
355
|
+
logger.info(' • Debug and trace support');
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Parse function call syntax: add(5, 3) -> { name: 'add', params: [5, 3] }
|
|
359
|
+
*/
|
|
360
|
+
function parseFunctionCall(functionOption) {
|
|
361
|
+
if (typeof functionOption === 'number') {
|
|
362
|
+
return { functionRef: functionOption, parameters: [] };
|
|
363
|
+
}
|
|
364
|
+
const functionStr = functionOption.toString().trim();
|
|
365
|
+
// Check if it's a function call syntax: name(params...)
|
|
366
|
+
const callMatch = functionStr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*(.*?)\s*\)$/);
|
|
367
|
+
if (callMatch) {
|
|
368
|
+
const [, functionName, paramsStr] = callMatch;
|
|
369
|
+
let parameters = [];
|
|
370
|
+
if (paramsStr.trim()) {
|
|
371
|
+
// Parse comma-separated parameters
|
|
372
|
+
try {
|
|
373
|
+
// Split by comma and parse each parameter
|
|
374
|
+
const paramStrs = paramsStr.split(',').map(p => p.trim());
|
|
375
|
+
parameters = paramStrs.map(parseValue);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
throw new Error(`Failed to parse function parameters in "${functionStr}": ${error}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return { functionRef: functionName, parameters };
|
|
382
|
+
}
|
|
383
|
+
// Check if it's a plain number (function index)
|
|
384
|
+
const numericValue = parseInt(functionStr, 10);
|
|
385
|
+
if (!isNaN(numericValue) && numericValue.toString() === functionStr) {
|
|
386
|
+
return { functionRef: numericValue, parameters: [] };
|
|
387
|
+
}
|
|
388
|
+
// Plain function name
|
|
389
|
+
return { functionRef: functionStr, parameters: [] };
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Parse parameters from various formats: space-separated, JSON string, or array
|
|
393
|
+
*/
|
|
394
|
+
function parseParameters(paramsOption) {
|
|
395
|
+
if (!paramsOption) {
|
|
396
|
+
return [];
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
// Handle arrays directly (from --params a b c)
|
|
400
|
+
if (Array.isArray(paramsOption)) {
|
|
401
|
+
return paramsOption.map(parseValue);
|
|
402
|
+
}
|
|
403
|
+
// Handle string input
|
|
404
|
+
if (typeof paramsOption === 'string') {
|
|
405
|
+
// JSON array format
|
|
406
|
+
if (paramsOption.startsWith('[') || paramsOption.startsWith('{')) {
|
|
407
|
+
return JSON.parse(paramsOption);
|
|
408
|
+
}
|
|
409
|
+
// Single parameter
|
|
410
|
+
return [parseValue(paramsOption)];
|
|
411
|
+
}
|
|
412
|
+
// Single value
|
|
413
|
+
return [parseValue(paramsOption)];
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
throw new Error(`Failed to parse parameters: ${error}. Use space-separated values like: -p 10 5, or JSON format: --parameters "[10, 5]"`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Parse a single parameter value, converting strings to appropriate types
|
|
421
|
+
*/
|
|
422
|
+
function parseValue(value) {
|
|
423
|
+
if (typeof value !== 'string') {
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
// Try to parse as number
|
|
427
|
+
const numValue = Number(value);
|
|
428
|
+
if (!isNaN(numValue) && isFinite(numValue)) {
|
|
429
|
+
return numValue;
|
|
430
|
+
}
|
|
431
|
+
// Try to parse as boolean
|
|
432
|
+
if (value.toLowerCase() === 'true')
|
|
433
|
+
return true;
|
|
434
|
+
if (value.toLowerCase() === 'false')
|
|
435
|
+
return false;
|
|
436
|
+
// Return as string
|
|
437
|
+
return value;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Display local execution results with clear formatting
|
|
441
|
+
*/
|
|
442
|
+
function displayLocalExecutionResult(result, options, logger) {
|
|
443
|
+
// Only show header in verbose mode
|
|
444
|
+
if (options.verbose || options.debug) {
|
|
445
|
+
console.log('\n' + section('Local Execution'));
|
|
446
|
+
}
|
|
447
|
+
if (result.success) {
|
|
448
|
+
console.log(uiSuccess('Execution succeeded'));
|
|
449
|
+
if (result.result !== undefined) {
|
|
450
|
+
console.log(` Result: ${JSON.stringify(result.result)}`);
|
|
451
|
+
}
|
|
452
|
+
if (result.executionTime) {
|
|
453
|
+
console.log(` Time: ${result.executionTime}ms`);
|
|
454
|
+
}
|
|
455
|
+
if (result.computeUnitsUsed !== undefined) {
|
|
456
|
+
console.log(` Compute units: ${result.computeUnitsUsed.toLocaleString()}`);
|
|
457
|
+
}
|
|
458
|
+
if (result.logs && result.logs.length > 0) {
|
|
459
|
+
console.log('\n' + section('Logs'));
|
|
460
|
+
result.logs.forEach((log) => {
|
|
461
|
+
console.log(` ${log}`);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
if (result.trace && options.trace) {
|
|
465
|
+
console.log('\n' + section('Trace'));
|
|
466
|
+
if (Array.isArray(result.trace)) {
|
|
467
|
+
result.trace.slice(0, 10).forEach((step, i) => {
|
|
468
|
+
console.log(` ${i}: ${JSON.stringify(step)}`);
|
|
469
|
+
});
|
|
470
|
+
if (result.trace.length > 10) {
|
|
471
|
+
console.log(` ... and ${result.trace.length - 10} more steps`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
console.log(uiError(result.error || 'Execution failed'));
|
|
478
|
+
// Enhanced VM debugging information for runtime errors
|
|
479
|
+
if (result.vmState || result.debug) {
|
|
480
|
+
const vmInfo = result.vmState || result.debug || result;
|
|
481
|
+
console.log('\n' + section('VM Debug'));
|
|
482
|
+
if (vmInfo.instructionPointer !== undefined) {
|
|
483
|
+
console.log(` Instruction Pointer: 0x${vmInfo.instructionPointer.toString(16).toUpperCase().padStart(4, '0')}`);
|
|
484
|
+
}
|
|
485
|
+
if (vmInfo.stoppedAtOpcode !== undefined) {
|
|
486
|
+
console.log(` Stopped at Opcode: 0x${vmInfo.stoppedAtOpcode.toString(16).toUpperCase().padStart(2, '0')} (${vmInfo.stoppedAtOpcode})`);
|
|
487
|
+
}
|
|
488
|
+
if (vmInfo.stoppedAtOpcodeName) {
|
|
489
|
+
console.log(` Opcode Name: ${vmInfo.stoppedAtOpcodeName}`);
|
|
490
|
+
}
|
|
491
|
+
if (vmInfo.errorMessage) {
|
|
492
|
+
console.log(` VM Error: ${vmInfo.errorMessage}`);
|
|
493
|
+
}
|
|
494
|
+
if (vmInfo.finalStack && Array.isArray(vmInfo.finalStack)) {
|
|
495
|
+
console.log(` Final Stack (${vmInfo.finalStack.length} items):`);
|
|
496
|
+
vmInfo.finalStack.slice(-5).forEach((item, i) => {
|
|
497
|
+
const index = vmInfo.finalStack.length - 5 + i;
|
|
498
|
+
console.log(` [${index}]: ${JSON.stringify(item)}`);
|
|
499
|
+
});
|
|
500
|
+
if (vmInfo.finalStack.length > 5) {
|
|
501
|
+
console.log(` ... (${vmInfo.finalStack.length - 5} more items)`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (vmInfo.executionContext) {
|
|
505
|
+
console.log(` Execution Context: ${vmInfo.executionContext}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (result.compilationErrors && result.compilationErrors.length > 0) {
|
|
509
|
+
console.log('\n Compilation errors:');
|
|
510
|
+
result.compilationErrors.forEach((error) => {
|
|
511
|
+
console.log(` - ${error.message || error}`);
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// JSON format output
|
|
516
|
+
if (options.format === 'json') {
|
|
517
|
+
console.log('\n' + section('JSON Output'));
|
|
518
|
+
console.log(JSON.stringify(result, null, 2));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Discover test files based on pattern
|
|
523
|
+
*/
|
|
524
|
+
async function discoverTestFiles(testPath, pattern) {
|
|
525
|
+
const testFiles = [];
|
|
526
|
+
try {
|
|
527
|
+
const stats = await stat(testPath);
|
|
528
|
+
if (stats.isFile()) {
|
|
529
|
+
// Single test file
|
|
530
|
+
if (testPath.endsWith('.test.json')) {
|
|
531
|
+
testFiles.push(testPath);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else if (stats.isDirectory()) {
|
|
535
|
+
// Directory - recursively find test files
|
|
536
|
+
const files = await readdir(testPath, { recursive: true });
|
|
537
|
+
for (const file of files) {
|
|
538
|
+
if (typeof file === 'string' && file.endsWith('.test.json')) {
|
|
539
|
+
testFiles.push(join(testPath, file));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
// Directory/file doesn't exist - that's ok
|
|
546
|
+
}
|
|
547
|
+
return testFiles;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Run tests locally using WASM VM
|
|
551
|
+
*/
|
|
552
|
+
async function runLocalTests(testFiles, options, logger) {
|
|
553
|
+
const results = [];
|
|
554
|
+
for (const testFile of testFiles) {
|
|
555
|
+
logger.info(`Running tests from: ${testFile}`);
|
|
556
|
+
try {
|
|
557
|
+
const content = await readFile(testFile, 'utf8');
|
|
558
|
+
const testSuite = JSON.parse(content);
|
|
559
|
+
const suiteName = testSuite.name || basename(testFile, '.test.json');
|
|
560
|
+
const testCases = testSuite.tests || testSuite.testCases || [];
|
|
561
|
+
// Apply filter if specified
|
|
562
|
+
let filteredTests = testCases;
|
|
563
|
+
if (options.filter) {
|
|
564
|
+
filteredTests = testCases.filter((test) => test.name?.includes(options.filter) || suiteName.includes(options.filter));
|
|
565
|
+
}
|
|
566
|
+
for (const testCase of filteredTests) {
|
|
567
|
+
const result = await runSingleLocalTest(testCase, options, logger);
|
|
568
|
+
results.push(result);
|
|
569
|
+
if (options.verbose) {
|
|
570
|
+
displaySingleTestResult(result, logger);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
results.push({
|
|
576
|
+
name: `Load ${basename(testFile)}`,
|
|
577
|
+
passed: false,
|
|
578
|
+
duration: 0,
|
|
579
|
+
error: `Failed to load test file: ${error}`
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return results;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Run a single test case locally
|
|
587
|
+
*/
|
|
588
|
+
async function runSingleLocalTest(testCase, options, logger) {
|
|
589
|
+
const startTime = Date.now();
|
|
590
|
+
try {
|
|
591
|
+
// Load bytecode
|
|
592
|
+
const bytecode = await readFile(testCase.bytecode);
|
|
593
|
+
// Parse input parameters
|
|
594
|
+
let parameters = [];
|
|
595
|
+
if (testCase.input) {
|
|
596
|
+
try {
|
|
597
|
+
const inputData = await readFile(testCase.input, 'utf8');
|
|
598
|
+
parameters = JSON.parse(inputData);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
parameters = [];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Execute with timeout
|
|
605
|
+
const executionPromise = FiveSDK.executeLocally(new Uint8Array(bytecode), testCase.functionIndex || 0, parameters, {
|
|
606
|
+
debug: options.verbose,
|
|
607
|
+
trace: options.verbose,
|
|
608
|
+
computeUnitLimit: options.maxCU
|
|
609
|
+
});
|
|
610
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Test timeout')), options.timeout));
|
|
611
|
+
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
612
|
+
const duration = Date.now() - startTime;
|
|
613
|
+
// Validate result
|
|
614
|
+
const passed = validateTestResult(result, testCase.expected || {});
|
|
615
|
+
return {
|
|
616
|
+
name: testCase.name,
|
|
617
|
+
passed,
|
|
618
|
+
duration,
|
|
619
|
+
details: options.verbose ? result : undefined
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
const duration = Date.now() - startTime;
|
|
624
|
+
// Check if error was expected
|
|
625
|
+
const passed = testCase.expected?.success === false &&
|
|
626
|
+
testCase.expected?.error !== undefined &&
|
|
627
|
+
error instanceof Error &&
|
|
628
|
+
error.message.includes(testCase.expected.error);
|
|
629
|
+
return {
|
|
630
|
+
name: testCase.name,
|
|
631
|
+
passed,
|
|
632
|
+
duration,
|
|
633
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Validate test result against expected outcome
|
|
639
|
+
*/
|
|
640
|
+
function validateTestResult(result, expected) {
|
|
641
|
+
// Check success/failure
|
|
642
|
+
if (result.success !== expected.success) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
// If expecting success, check result value
|
|
646
|
+
if (expected.success && expected.result !== undefined) {
|
|
647
|
+
if (JSON.stringify(result.result) !== JSON.stringify(expected.result)) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Check compute units limit
|
|
652
|
+
if (expected.maxComputeUnits && result.computeUnitsUsed > expected.maxComputeUnits) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Display single test result
|
|
659
|
+
*/
|
|
660
|
+
function displaySingleTestResult(result, logger) {
|
|
661
|
+
const status = result.passed ? 'OK' : 'FAIL';
|
|
662
|
+
const duration = `(${result.duration}ms)`;
|
|
663
|
+
console.log(` ${status} ${result.name} ${duration}`);
|
|
664
|
+
if (!result.passed && result.error) {
|
|
665
|
+
console.log(` Error: ${result.error}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Display comprehensive test results
|
|
670
|
+
*/
|
|
671
|
+
function displayTestResults(results, options, logger) {
|
|
672
|
+
if (options.format === 'json') {
|
|
673
|
+
console.log(JSON.stringify(results, null, 2));
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
console.log('\n' + section('Local Tests'));
|
|
677
|
+
const passed = results.filter(r => r.passed).length;
|
|
678
|
+
const total = results.length;
|
|
679
|
+
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
|
|
680
|
+
// Summary
|
|
681
|
+
console.log(`\nSummary:`);
|
|
682
|
+
console.log(` Total: ${total} tests`);
|
|
683
|
+
console.log(` Passed: ${passed}`);
|
|
684
|
+
const failed = total - passed;
|
|
685
|
+
if (failed > 0) {
|
|
686
|
+
console.log(` Failed: ${failed}`);
|
|
687
|
+
// Show failed tests
|
|
688
|
+
if (!options.verbose) {
|
|
689
|
+
console.log('\nFailed tests:');
|
|
690
|
+
results.filter(r => !r.passed).forEach(result => {
|
|
691
|
+
console.log(` FAIL ${result.name}: ${result.error || 'Test failed'}`);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
console.log(` Duration: ${totalDuration}ms`);
|
|
696
|
+
if (failed === 0) {
|
|
697
|
+
console.log(uiSuccess('All tests passed'));
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
console.log(uiError(`${failed} test(s) failed`));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
//# sourceMappingURL=local.js.map
|