@5ive-tech/sdk 1.1.7 → 1.1.8

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.
Binary file
@@ -6,7 +6,7 @@
6
6
  * Baked program ID injected at release time (set by scripts/set-default-program-id.sh)
7
7
  * Empty string by default; overridden in npm published packages.
8
8
  */
9
- export declare const FIVE_BAKED_PROGRAM_ID = "";
9
+ export declare const FIVE_BAKED_PROGRAM_ID = "4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d";
10
10
  /**
11
11
  * Centralized resolver for program IDs across all SDK operations.
12
12
  * Ensures consistent validation and error messaging.
@@ -7,7 +7,7 @@ import { validator } from '../validation/index.js';
7
7
  * Baked program ID injected at release time (set by scripts/set-default-program-id.sh)
8
8
  * Empty string by default; overridden in npm published packages.
9
9
  */
10
- export const FIVE_BAKED_PROGRAM_ID = '';
10
+ export const FIVE_BAKED_PROGRAM_ID = '4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d';
11
11
  /**
12
12
  * Centralized resolver for program IDs across all SDK operations.
13
13
  * Ensures consistent validation and error messaging.
@@ -219,7 +219,7 @@ export class AccountTestFixture {
219
219
  else if (spec.type === 'state' || spec.type === 'mutable') {
220
220
  // Create state/mutable account with initial data
221
221
  const space = 1024; // Default space
222
- const owner = options.fiveVMProgramId || new PublicKey('9MHGM73eszNUtmJS6ypDCESguxWhCBnkUPpTMyLGqURH');
222
+ const owner = options.fiveVMProgramId || new PublicKey('4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d');
223
223
  // Serialize state data if provided
224
224
  let initialData;
225
225
  if (spec.state && spec.type === 'state') {
@@ -242,7 +242,7 @@ export class AccountTestFixture {
242
242
  else if (spec.type === 'init') {
243
243
  // Create init account (will be initialized by script)
244
244
  const space = 1024;
245
- const owner = options.fiveVMProgramId || new PublicKey('9MHGM73eszNUtmJS6ypDCESguxWhCBnkUPpTMyLGqURH');
245
+ const owner = options.fiveVMProgramId || new PublicKey('4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d');
246
246
  publicKey = await manager.createAccount(space, owner);
247
247
  if (options.debug) {
248
248
  console.log(` ${spec.name} (init): ${publicKey.toString()}`);
@@ -256,7 +256,7 @@ export class AccountTestFixture {
256
256
  else {
257
257
  // Create readonly account
258
258
  const space = 0;
259
- const owner = options.fiveVMProgramId || new PublicKey('9MHGM73eszNUtmJS6ypDCESguxWhCBnkUPpTMyLGqURH');
259
+ const owner = options.fiveVMProgramId || new PublicKey('4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d');
260
260
  publicKey = await manager.createAccount(space, owner);
261
261
  if (options.debug) {
262
262
  console.log(` ${spec.name} (readonly): ${publicKey.toString()}`);
@@ -13,6 +13,8 @@ export interface VSourceTest {
13
13
  file: string;
14
14
  functionName: string;
15
15
  parameters?: any[];
16
+ expectedResult?: any;
17
+ expectsResult?: boolean;
16
18
  description?: string;
17
19
  }
18
20
  /**
@@ -36,6 +38,8 @@ export interface DiscoveredTest {
36
38
  source?: VSourceTest;
37
39
  description?: string;
38
40
  parameters?: any[];
41
+ expectedResult?: any;
42
+ expectsResult?: boolean;
39
43
  }
40
44
  /**
41
45
  * Discover tests from directory
@@ -64,6 +68,8 @@ export declare class TestDiscovery {
64
68
  * Parse .v source file for test functions and parameters
65
69
  */
66
70
  private static parseVFile;
71
+ private static splitParamsAndExpectation;
72
+ private static parseTokenValue;
67
73
  /**
68
74
  * Compile a .v test source file
69
75
  */
@@ -6,7 +6,7 @@
6
6
  * extracts parameters from @test-params comments, and compiles source files.
7
7
  */
8
8
  import { readFile, readdir, stat } from 'fs/promises';
9
- import { join, basename } from 'path';
9
+ import { basename, join } from 'path';
10
10
  import { FiveSDK } from '../FiveSDK.js';
11
11
  /**
12
12
  * Discover tests from directory
@@ -121,80 +121,62 @@ export class TestDiscovery {
121
121
  const tests = [];
122
122
  try {
123
123
  const content = await readFile(file, 'utf8');
124
- // Find all function definitions with potential test annotations
125
- // Pattern 1: pub function with #[test] annotation
126
- // Pattern 2: function with specific naming convention (test_*, *_test)
127
124
  const lines = content.split('\n');
128
- let currentFunction = null;
129
- let currentParams = null;
130
- let currentDescription = null;
125
+ let pendingParams;
131
126
  for (let i = 0; i < lines.length; i++) {
132
127
  const line = lines[i].trim();
133
128
  // Check for @test-params comment
134
- const paramsMatch = line.match(/@test-params\s+(.*)/);
129
+ const paramsMatch = line.match(/@test-params(?:\s+(.*))?$/);
135
130
  if (paramsMatch) {
136
131
  try {
137
- const paramsStr = paramsMatch[1].trim();
138
- // Try to parse as JSON array first
139
- if (paramsStr.startsWith('[')) {
140
- currentParams = JSON.parse(paramsStr);
132
+ const paramsStr = (paramsMatch[1] || '').trim();
133
+ if (paramsStr.length === 0) {
134
+ pendingParams = [];
135
+ }
136
+ else if (paramsStr.startsWith('[')) {
137
+ const parsed = JSON.parse(paramsStr);
138
+ pendingParams = Array.isArray(parsed) ? parsed : [];
141
139
  }
142
140
  else {
143
- // Parse space-separated values
144
- currentParams = paramsStr.split(/\s+/).map(p => {
145
- // Try to parse as number
146
- if (!isNaN(Number(p))) {
147
- return Number(p);
148
- }
149
- return p;
150
- });
141
+ pendingParams = paramsStr
142
+ .split(/\s+/)
143
+ .filter(Boolean)
144
+ .map((token) => this.parseTokenValue(token));
151
145
  }
152
146
  }
153
147
  catch (error) {
154
148
  console.warn(`Failed to parse @test-params in ${file}:${i + 1}: ${line}`);
149
+ pendingParams = undefined;
155
150
  }
156
151
  continue;
157
152
  }
158
- // Check for test annotation
159
- if (line.startsWith('#[test]') || line.includes('#[test]')) {
160
- // Next non-empty line should be the function definition
161
- for (let j = i + 1; j < lines.length; j++) {
162
- const nextLine = lines[j].trim();
163
- if (nextLine && !nextLine.startsWith('//')) {
164
- const funcMatch = nextLine.match(/(?:pub\s+)?(?:fn|instruction|script)\s+(\w+)\s*\(/);
165
- if (funcMatch) {
166
- currentFunction = funcMatch[1];
167
- break;
168
- }
169
- }
170
- }
171
- continue;
172
- }
173
- // Check for pub function that matches test naming convention
174
- const funcMatch = line.match(/pub\s+(?:fn|instruction|script)\s+(test_\w+|_?\w+_test)\s*\(/);
153
+ // Match canonical DSL test function forms:
154
+ // pub test_name(...)
155
+ // pub fn test_name(...)
156
+ const funcMatch = line.match(/^pub\s+(?:fn\s+)?(test_[A-Za-z0-9_]*|[A-Za-z0-9_]*_test)\s*\([^)]*\)\s*(?:->\s*([A-Za-z0-9_<>\[\]]+))?/);
175
157
  if (funcMatch) {
176
158
  const functionName = funcMatch[1];
177
- // Check if we have parameters from @test-params comment
178
- if (currentParams || currentFunction) {
179
- const name = basename(file, '.v') + '::' + functionName;
180
- tests.push({
181
- name,
182
- path: file,
159
+ const returnType = funcMatch[2];
160
+ const hasReturnValue = !!returnType;
161
+ const [parameters, expectedResult, expectsResult] = this.splitParamsAndExpectation(pendingParams, hasReturnValue);
162
+ const name = `${basename(file, '.v')}::${functionName}`;
163
+ tests.push({
164
+ name,
165
+ path: file,
166
+ type: 'v-source',
167
+ source: {
183
168
  type: 'v-source',
184
- source: {
185
- type: 'v-source',
186
- file,
187
- functionName,
188
- parameters: currentParams || undefined,
189
- description: currentDescription || undefined
190
- },
191
- parameters: currentParams || undefined,
192
- description: currentDescription || undefined
193
- });
194
- currentParams = null;
195
- currentDescription = null;
196
- currentFunction = null;
197
- }
169
+ file,
170
+ functionName,
171
+ parameters: parameters.length > 0 ? parameters : undefined,
172
+ expectedResult,
173
+ expectsResult
174
+ },
175
+ parameters: parameters.length > 0 ? parameters : undefined,
176
+ expectedResult,
177
+ expectsResult
178
+ });
179
+ pendingParams = undefined;
198
180
  }
199
181
  }
200
182
  }
@@ -203,6 +185,29 @@ export class TestDiscovery {
203
185
  }
204
186
  return tests;
205
187
  }
188
+ static splitParamsAndExpectation(values, hasReturnValue) {
189
+ const parsed = Array.isArray(values) ? values : [];
190
+ if (!hasReturnValue || parsed.length === 0) {
191
+ return [parsed, undefined, false];
192
+ }
193
+ const params = parsed.slice(0, parsed.length - 1);
194
+ return [params, parsed[parsed.length - 1], true];
195
+ }
196
+ static parseTokenValue(token) {
197
+ if ((token.startsWith('"') && token.endsWith('"')) ||
198
+ (token.startsWith("'") && token.endsWith("'"))) {
199
+ return token.slice(1, -1);
200
+ }
201
+ if (token === 'true')
202
+ return true;
203
+ if (token === 'false')
204
+ return false;
205
+ const asNumber = Number(token);
206
+ if (!Number.isNaN(asNumber)) {
207
+ return asNumber;
208
+ }
209
+ return token;
210
+ }
206
211
  /**
207
212
  * Compile a .v test source file
208
213
  */
@@ -76,6 +76,7 @@ export interface TestRunnerOptions {
76
76
  */
77
77
  export declare class FiveTestRunner {
78
78
  private options;
79
+ private compilationCache;
79
80
  constructor(options?: TestRunnerOptions);
80
81
  /**
81
82
  * Run a single test case
@@ -111,7 +112,6 @@ export declare class FiveTestRunner {
111
112
  private getValidationError;
112
113
  private matchesPattern;
113
114
  private chunkArray;
114
- private findTestFiles;
115
115
  private formatTestResult;
116
116
  private formatSuiteResult;
117
117
  }
@@ -4,14 +4,16 @@
4
4
  * SDK-based test utilities that replace shell script approaches with programmatic
5
5
  * Five SDK usage. Provides comprehensive testing capabilities for Five VM scripts.
6
6
  */
7
- import { readFile, readdir } from 'fs/promises';
8
- import { join, basename } from 'path';
7
+ import { readFile } from 'fs/promises';
8
+ import { basename } from 'path';
9
9
  import { FiveSDK } from '../FiveSDK.js';
10
+ import { TestDiscovery } from './TestDiscovery.js';
10
11
  /**
11
12
  * Five SDK-based test runner
12
13
  */
13
14
  export class FiveTestRunner {
14
15
  constructor(options = {}) {
16
+ this.compilationCache = new Map();
15
17
  this.options = {
16
18
  timeout: 30000,
17
19
  maxComputeUnits: 1000000,
@@ -40,13 +42,22 @@ export class FiveTestRunner {
40
42
  };
41
43
  }
42
44
  let bytecode;
45
+ let abi;
43
46
  // Get bytecode (compile source or load existing)
44
47
  if (testCase.source) {
45
- const result = await this.compileToBytecode(testCase.source);
46
- if (!result.success || !result.bytecode) {
47
- throw new Error(`Compilation failed: ${result.errors?.join(', ')}`);
48
+ if (!this.compilationCache.has(testCase.source)) {
49
+ const result = await this.compileToBytecode(testCase.source);
50
+ if (!result.success || !result.bytecode) {
51
+ throw new Error(`Compilation failed: ${result.errors?.join(', ')}`);
52
+ }
53
+ this.compilationCache.set(testCase.source, {
54
+ bytecode: result.bytecode,
55
+ abi: result.abi
56
+ });
48
57
  }
49
- bytecode = result.bytecode;
58
+ const cached = this.compilationCache.get(testCase.source);
59
+ bytecode = cached.bytecode;
60
+ abi = cached.abi;
50
61
  }
51
62
  else if (testCase.bytecode) {
52
63
  const data = await readFile(testCase.bytecode);
@@ -67,7 +78,8 @@ export class FiveTestRunner {
67
78
  const executionPromise = FiveSDK.executeLocally(bytecode, testCase.function || 0, testCase.parameters || [], {
68
79
  debug: this.options.debug,
69
80
  trace: this.options.trace,
70
- computeUnitLimit: this.options.maxComputeUnits
81
+ computeUnitLimit: this.options.maxComputeUnits,
82
+ abi
71
83
  });
72
84
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Test timeout')), executionTimeout));
73
85
  const result = await Promise.race([executionPromise, timeoutPromise]);
@@ -187,23 +199,52 @@ export class FiveTestRunner {
187
199
  * Discover and load test suites from directory
188
200
  */
189
201
  async discoverTestSuites(directory, pattern = '**/*.test.json') {
190
- const testFiles = await this.findTestFiles(directory, pattern);
202
+ const discovered = await TestDiscovery.discoverTests(directory, { verbose: this.options.verbose });
191
203
  const suites = [];
192
- for (const file of testFiles) {
193
- try {
194
- const content = await readFile(file, 'utf8');
195
- const data = JSON.parse(content);
196
- const suite = {
197
- name: data.name || basename(file, '.test.json'),
198
- description: data.description,
199
- testCases: data.tests || data.testCases || []
200
- };
201
- suites.push(suite);
204
+ const byFile = new Map();
205
+ const loadedJsonSuites = new Set();
206
+ for (const test of discovered) {
207
+ if (test.type === 'json-suite') {
208
+ if (loadedJsonSuites.has(test.path)) {
209
+ continue;
210
+ }
211
+ try {
212
+ const content = await readFile(test.path, 'utf8');
213
+ const data = JSON.parse(content);
214
+ suites.push({
215
+ name: data.name || basename(test.path, '.test.json'),
216
+ description: data.description,
217
+ testCases: data.tests || data.testCases || []
218
+ });
219
+ loadedJsonSuites.add(test.path);
220
+ }
221
+ catch (error) {
222
+ console.warn(`Failed to load test suite ${test.path}: ${error}`);
223
+ }
224
+ continue;
202
225
  }
203
- catch (error) {
204
- console.warn(`Failed to load test suite ${file}: ${error}`);
226
+ if (test.type === 'v-source' && test.source) {
227
+ const cases = byFile.get(test.path) || [];
228
+ cases.push({
229
+ name: test.name,
230
+ source: test.path,
231
+ function: test.source.functionName,
232
+ parameters: test.parameters || [],
233
+ expected: {
234
+ success: true,
235
+ result: test.expectsResult ? test.expectedResult : undefined
236
+ }
237
+ });
238
+ byFile.set(test.path, cases);
205
239
  }
206
240
  }
241
+ for (const [file, testCases] of byFile.entries()) {
242
+ suites.push({
243
+ name: basename(file, '.v'),
244
+ description: `Tests from ${file}`,
245
+ testCases
246
+ });
247
+ }
207
248
  return suites;
208
249
  }
209
250
  /**
@@ -245,7 +286,7 @@ export class FiveTestRunner {
245
286
  // Private helper methods
246
287
  async compileToBytecode(sourcePath) {
247
288
  const source = await readFile(sourcePath, 'utf8');
248
- return FiveSDK.compile(source, { debug: this.options.debug });
289
+ return FiveSDK.compile({ filename: sourcePath, content: source }, { debug: this.options.debug });
249
290
  }
250
291
  validateResult(result, expected) {
251
292
  // Check success/failure
@@ -298,26 +339,6 @@ export class FiveTestRunner {
298
339
  }
299
340
  return chunks;
300
341
  }
301
- async findTestFiles(directory, pattern) {
302
- const files = [];
303
- try {
304
- const entries = await readdir(directory, { withFileTypes: true });
305
- for (const entry of entries) {
306
- const fullPath = join(directory, entry.name);
307
- if (entry.isDirectory()) {
308
- const subFiles = await this.findTestFiles(fullPath, pattern);
309
- files.push(...subFiles);
310
- }
311
- else if (entry.isFile() && entry.name.endsWith('.test.json')) {
312
- files.push(fullPath);
313
- }
314
- }
315
- }
316
- catch (error) {
317
- // Directory doesn't exist or not accessible
318
- }
319
- return files;
320
- }
321
342
  formatTestResult(result) {
322
343
  const icon = result.passed ? '✅' : '❌';
323
344
  const duration = `${result.duration}ms`;
package/dist/types.d.ts CHANGED
@@ -203,7 +203,7 @@ export interface CLIError extends Error {
203
203
  category: string;
204
204
  details?: any;
205
205
  }
206
- export declare const FIVE_VM_PROGRAM_ID = "Five111111111111111111111111111111111111111";
206
+ export declare const FIVE_VM_PROGRAM_ID = "4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d";
207
207
  export interface FiveSDKConfig {
208
208
  network?: string;
209
209
  connection?: any;
package/dist/types.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Type definitions for Five SDK.
3
3
  */
4
4
  // ==================== Legacy SDK Types (for compatibility) ====================
5
- export const FIVE_VM_PROGRAM_ID = "Five111111111111111111111111111111111111111";
5
+ export const FIVE_VM_PROGRAM_ID = "4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d";
6
6
  export class FiveSDKError extends Error {
7
7
  constructor(message, code, details) {
8
8
  super(message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@5ive-tech/sdk",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Client-agnostic TypeScript SDK for Five VM scripts on Solana",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -56,4 +56,4 @@
56
56
  "publishConfig": {
57
57
  "access": "public"
58
58
  }
59
- }
59
+ }