@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,806 @@
1
+ // Deploy command.
2
+ import { readFile, writeFile } from 'fs/promises';
3
+ import { isAbsolute, join } from 'path';
4
+ import ora from 'ora';
5
+ import { Connection, Keypair } from '@solana/web3.js';
6
+ import { parse as parseToml, stringify as stringifyToml } from '@iarna/toml';
7
+ import { FiveSDK, ProgramIdResolver } from '@5ive-tech/sdk';
8
+ import { ConfigManager } from '../config/ConfigManager.js';
9
+ import { FiveFileManager } from '../utils/FiveFileManager.js';
10
+ import { computeHash, loadBuildManifest, loadProjectConfig } from '../project/ProjectLoader.js';
11
+ import { section, keyValue, success as uiSuccess, error as uiError, isTTY } from '../utils/cli-ui.js';
12
+ /**
13
+ * Five deploy command implementation
14
+ */
15
+ export const deployCommand = {
16
+ name: 'deploy',
17
+ description: 'Deploy bytecode to Solana',
18
+ aliases: ['d'],
19
+ options: [
20
+ {
21
+ flags: '-t, --target <target>',
22
+ description: 'Override target network (devnet, testnet, mainnet, local)',
23
+ required: false
24
+ },
25
+ {
26
+ flags: '-n, --network <url>',
27
+ description: 'Override network RPC URL',
28
+ required: false
29
+ },
30
+ {
31
+ flags: '-k, --keypair <file>',
32
+ description: 'Override keypair file path',
33
+ required: false
34
+ },
35
+ {
36
+ flags: '--program-id <pubkey>',
37
+ description: 'Specify the Five VM program ID to be the owner of the new script account',
38
+ required: false
39
+ },
40
+ {
41
+ flags: '--vm-state-account <pubkey>',
42
+ description: 'Specify an existing VM State Account (skips creation)',
43
+ required: false
44
+ },
45
+ {
46
+ flags: '--max-data-size <size>',
47
+ description: 'Maximum data size for program account (bytes)',
48
+ defaultValue: 1000000
49
+ },
50
+ {
51
+ flags: '--compute-budget <units>',
52
+ description: 'Compute budget for deployment transaction',
53
+ defaultValue: 1400000
54
+ },
55
+ {
56
+ flags: '--skip-verification',
57
+ description: 'Skip deployment verification',
58
+ defaultValue: false
59
+ },
60
+ {
61
+ flags: '--dry-run',
62
+ description: 'Simulate deployment without executing',
63
+ defaultValue: false
64
+ },
65
+ {
66
+ flags: '--format <format>',
67
+ description: 'Output format',
68
+ choices: ['text', 'json'],
69
+ defaultValue: 'text'
70
+ },
71
+ {
72
+ flags: '--debug',
73
+ description: 'Enable debug output',
74
+ defaultValue: false
75
+ },
76
+ {
77
+ flags: '--chunk-size <bytes>',
78
+ description: 'Chunk size for large program deployment (default: 750)',
79
+ defaultValue: 750
80
+ },
81
+ {
82
+ flags: '--progress',
83
+ description: 'Show progress for large program deployments',
84
+ defaultValue: false
85
+ },
86
+ {
87
+ flags: '--force-chunked',
88
+ description: 'Force chunked deployment even for small programs',
89
+ defaultValue: false
90
+ },
91
+ {
92
+ flags: '--optimized',
93
+ description: 'Use optimized deployment instructions (50-70% fewer transactions)',
94
+ defaultValue: false
95
+ },
96
+ {
97
+ flags: '--project <path>',
98
+ description: 'Project directory or five.toml path',
99
+ required: false
100
+ },
101
+ {
102
+ flags: '--namespace <scoped>',
103
+ description: 'Bind deployed script to scoped namespace (e.g. @5ive-tech/program)',
104
+ required: false
105
+ },
106
+ {
107
+ flags: '--namespace-manager <script>',
108
+ description: 'Namespace manager script account (overrides project/env/lockfile)',
109
+ required: false
110
+ }
111
+ ],
112
+ arguments: [
113
+ {
114
+ name: 'bytecode',
115
+ description: 'Five VM artifact file (.five or .bin)',
116
+ required: true
117
+ }
118
+ ],
119
+ examples: [
120
+ {
121
+ command: 'five deploy program.five',
122
+ description: 'Deploy to configured target (uses config defaults)'
123
+ },
124
+ {
125
+ command: 'five deploy program.five --target mainnet',
126
+ description: 'Deploy to mainnet (overrides config)'
127
+ },
128
+ {
129
+ command: 'five deploy program.five --keypair deployer.json --target devnet',
130
+ description: 'Deploy to devnet with specific keypair'
131
+ },
132
+ {
133
+ command: 'five deploy program.five --dry-run --format json',
134
+ description: 'Simulate deployment with JSON output'
135
+ },
136
+ {
137
+ command: 'five deploy large-program.bin --optimized --progress',
138
+ description: 'Deploy large program with optimized instructions (50-70% fewer transactions)'
139
+ }
140
+ ],
141
+ handler: async (args, options, context) => {
142
+ const { logger } = context;
143
+ if (context.options.debug) {
144
+ logger.debug(`deploy args: ${JSON.stringify(args)}`);
145
+ logger.debug(`deploy options: ${JSON.stringify(options)}`);
146
+ }
147
+ try {
148
+ const projectContext = await loadProjectConfig(options.project, process.cwd());
149
+ const manifest = projectContext ? await loadBuildManifest(projectContext.rootDir) : null;
150
+ // Apply project defaults to options if not provided
151
+ if (!options.target && projectContext?.config.cluster) {
152
+ options.target = projectContext.config.cluster;
153
+ }
154
+ if (!options.network && projectContext?.config.rpcUrl) {
155
+ options.network = projectContext.config.rpcUrl;
156
+ }
157
+ if (!options.keypair && projectContext?.config.keypairPath) {
158
+ options.keypair = projectContext.config.keypairPath;
159
+ }
160
+ if (context.options.verbose) {
161
+ logger.info('Applying configuration overrides...');
162
+ }
163
+ // Apply config with CLI overrides
164
+ const configManager = ConfigManager.getInstance();
165
+ const overrides = {
166
+ target: options.target,
167
+ network: options.network,
168
+ keypair: options.keypair
169
+ };
170
+ if (context.options.debug) {
171
+ logger.debug(`overrides: ${JSON.stringify(overrides)}`);
172
+ }
173
+ const config = await configManager.applyOverrides(overrides);
174
+ // Resolve program ID AFTER target override is applied, using the correct target
175
+ // Precedence: CLI flag → project config → config file (per-target) → env var
176
+ if (!options.programId) {
177
+ const configuredProgramId = await configManager.getProgramId(config.target);
178
+ options.programId = projectContext?.config.programId || configuredProgramId || process.env.FIVE_PROGRAM_ID;
179
+ }
180
+ if (context.options.debug) {
181
+ logger.debug(`config: ${JSON.stringify(config, null, 2)}`);
182
+ }
183
+ // Show target context prefix
184
+ const targetPrefix = ConfigManager.getTargetPrefix(config.target);
185
+ if (context.options.verbose) {
186
+ logger.info(`${targetPrefix} Deploying Five VM bytecode`);
187
+ }
188
+ // Show config details if enabled
189
+ if (config.showConfig && context.options.verbose) {
190
+ logger.info(`Target: ${config.target}`);
191
+ logger.info(`Network: ${config.networks[config.target].rpcUrl}`);
192
+ logger.info(`Keypair: ${config.keypairPath}`);
193
+ }
194
+ // Load bytecode using centralized file manager
195
+ let bytecodeFile = args[0] || manifest?.artifact_path;
196
+ if (bytecodeFile && projectContext && !isAbsolute(bytecodeFile)) {
197
+ bytecodeFile = join(projectContext.rootDir, bytecodeFile);
198
+ }
199
+ if (!bytecodeFile) {
200
+ throw new Error('Bytecode file is required (pass a path or compile to generate .five/.bin and manifest)');
201
+ }
202
+ if (context.options.verbose) {
203
+ logger.info(`Loading bytecode: ${bytecodeFile}`);
204
+ }
205
+ const spinner = isTTY() ? ora('Loading bytecode...').start() : null;
206
+ const fileManager = FiveFileManager.getInstance();
207
+ const loadedFile = await fileManager.loadFile(bytecodeFile, {
208
+ validateFormat: true
209
+ });
210
+ if (spinner) {
211
+ spinner.succeed(`Loaded ${loadedFile.format.toUpperCase()} (${loadedFile.bytecode.length} bytes)`);
212
+ }
213
+ if (context.options.verbose) {
214
+ logger.info(`Loaded ${loadedFile.bytecode.length} bytes`);
215
+ }
216
+ // Show additional info for .five files
217
+ if (loadedFile.abi) {
218
+ const functionCount = Object.keys(loadedFile.abi.functions || {}).length;
219
+ if (context.options.verbose) {
220
+ logger.info(`ABI functions: ${functionCount}`);
221
+ }
222
+ if (functionCount > 0) {
223
+ const functionNames = Object.keys(loadedFile.abi.functions).join(', ');
224
+ if (context.options.debug) {
225
+ logger.debug(`ABI functions: ${functionNames}`);
226
+ }
227
+ }
228
+ }
229
+ // Validate bytecode using Five SDK
230
+ if (context.options.verbose) {
231
+ logger.info('Validating bytecode...');
232
+ }
233
+ if (spinner) {
234
+ spinner.start('Validating bytecode...');
235
+ }
236
+ const validation = await FiveSDK.validateBytecode(loadedFile.bytecode, {
237
+ debug: options.debug || false
238
+ });
239
+ if (context.options.debug) {
240
+ logger.debug(`validation: ${JSON.stringify(validation)}`);
241
+ }
242
+ if (!validation.valid) {
243
+ if (spinner) {
244
+ spinner.fail('Bytecode validation failed');
245
+ }
246
+ logger.error(`Validation failed: ${validation.errors?.join(', ')}`);
247
+ throw new Error(`Invalid bytecode: ${validation.errors?.join(', ')}`);
248
+ }
249
+ if (spinner) {
250
+ spinner.succeed('Bytecode validation passed');
251
+ }
252
+ if (context.options.verbose) {
253
+ logger.info('Bytecode validation passed');
254
+ }
255
+ // Validate bytecode size
256
+ if (context.options.debug) {
257
+ logger.debug(`bytecode size: ${loadedFile.bytecode.length} bytes (max: ${options.maxDataSize})`);
258
+ }
259
+ if (loadedFile.bytecode.length > options.maxDataSize) {
260
+ throw new Error(`Bytecode too large: ${loadedFile.bytecode.length} bytes (max: ${options.maxDataSize})`);
261
+ }
262
+ // Setup deployment options
263
+ if (context.options.verbose) {
264
+ logger.info('Preparing deployment options...');
265
+ }
266
+ const deploymentOptions = await setupDeploymentOptions(loadedFile.bytecode, loadedFile.abi, options, config, logger);
267
+ if (context.options.debug) {
268
+ logger.debug(`deployment options: ${JSON.stringify(deploymentOptions, null, 2)}`);
269
+ }
270
+ // Validate program ID for on-chain deployment
271
+ let resolvedProgramId;
272
+ if (config.target !== 'wasm') {
273
+ try {
274
+ resolvedProgramId = ProgramIdResolver.resolve(options.programId);
275
+ }
276
+ catch (error) {
277
+ throw new Error(`Program ID required for deployment to ${config.target}. ` +
278
+ `Provide via: --program-id <pubkey>, five.toml programId, ` +
279
+ `or: five config set --program-id <pubkey>`);
280
+ }
281
+ options.programId = resolvedProgramId;
282
+ }
283
+ // Fetch current fees from VM state to determine if extra accounts are needed
284
+ let fees;
285
+ try {
286
+ const rpcUrl = config.networks[config.target].rpcUrl;
287
+ const connection = new Connection(rpcUrl, 'confirmed');
288
+ fees = await FiveSDK.getFees(connection, options.programId);
289
+ if (fees.deployFeeBps > 0 || fees.executeFeeBps > 0) {
290
+ if (context.options.verbose || options.debug) {
291
+ console.log('\n' + section('VM Fees'));
292
+ if (fees.deployFeeBps > 0) {
293
+ console.log(keyValue('Deployment Fee', `${(fees.deployFeeBps / 100).toFixed(2)}%`));
294
+ }
295
+ if (fees.executeFeeBps > 0) {
296
+ console.log(keyValue('Execution Fee', `${(fees.executeFeeBps / 100).toFixed(2)}%`));
297
+ }
298
+ if (fees.adminAccount) {
299
+ console.log(keyValue('Admin Account', fees.adminAccount));
300
+ }
301
+ }
302
+ // Attach admin account to options for use in deployment
303
+ options.adminAccount = fees.adminAccount;
304
+ }
305
+ }
306
+ catch (e) {
307
+ if (context.options.debug) {
308
+ logger.debug(`Could not fetch VM fees: ${e instanceof Error ? e.message : String(e)}`);
309
+ }
310
+ }
311
+ // Execute deployment
312
+ let result;
313
+ const namespaceManagerScript = options.namespace && projectContext
314
+ ? await resolveNamespaceManagerScript(projectContext.rootDir, projectContext.config.namespaceManager, options.namespaceManager)
315
+ : undefined;
316
+ if (options.dryRun) {
317
+ if (context.options.verbose) {
318
+ logger.info('Simulating deployment...');
319
+ }
320
+ result = await simulateDeployment(deploymentOptions, options, context);
321
+ }
322
+ else {
323
+ if (options.namespace && projectContext && !namespaceManagerScript) {
324
+ await validateNamespaceOwnership(projectContext.rootDir, options.namespace, config.keypairPath);
325
+ }
326
+ if (context.options.verbose) {
327
+ logger.info('Deploying to network...');
328
+ }
329
+ if (context.options.debug) {
330
+ logger.debug(`deployment options: ${JSON.stringify(deploymentOptions)}`);
331
+ logger.debug(`config: ${JSON.stringify(config)}`);
332
+ }
333
+ // Ensure admin account is passed into deployment options
334
+ const execDeploymentOptions = {
335
+ ...deploymentOptions,
336
+ adminAccount: options.adminAccount
337
+ };
338
+ result = await executeDeployment(execDeploymentOptions, options, context, config);
339
+ }
340
+ // Display results
341
+ if (context.options.verbose) {
342
+ logger.info('Deployment completed');
343
+ }
344
+ if (result.success && result.programId && projectContext) {
345
+ try {
346
+ await updateLockfileExports(projectContext.rootDir, projectContext.config.name, result.programId, loadedFile.bytecode, deploymentOptions.exportMetadata);
347
+ }
348
+ catch (e) {
349
+ if (context.options.debug) {
350
+ logger.debug(`lockfile export cache update failed: ${e instanceof Error ? e.message : String(e)}`);
351
+ }
352
+ }
353
+ if (options.namespace) {
354
+ if (namespaceManagerScript && !options.dryRun) {
355
+ const rpcUrl = config.networks[config.target].rpcUrl;
356
+ const connection = new Connection(rpcUrl, 'confirmed');
357
+ const signerKeypair = await loadKeypair(config.keypairPath, logger);
358
+ const bindResult = await FiveSDK.bindNamespaceOnChain(options.namespace, result.programId, {
359
+ managerScriptAccount: namespaceManagerScript,
360
+ connection,
361
+ signerKeypair,
362
+ fiveVMProgramId: options.programId || deploymentOptions.fiveVMProgramId,
363
+ debug: options.debug || context.options.debug || false,
364
+ });
365
+ if (context.options.verbose) {
366
+ logger.info(`Namespace bound on-chain via ${namespaceManagerScript}`);
367
+ if (bindResult.transactionId) {
368
+ logger.info(`Namespace bind tx: ${bindResult.transactionId}`);
369
+ }
370
+ }
371
+ }
372
+ try {
373
+ await updateLockfileNamespace(projectContext.rootDir, options.namespace, result.programId);
374
+ if (namespaceManagerScript) {
375
+ await updateLockfileNamespaceManager(projectContext.rootDir, namespaceManagerScript);
376
+ }
377
+ }
378
+ catch (e) {
379
+ if (context.options.debug) {
380
+ logger.debug(`lockfile namespace cache update failed: ${e instanceof Error ? e.message : String(e)}`);
381
+ }
382
+ }
383
+ }
384
+ }
385
+ displayDeploymentResult(result, options, logger);
386
+ if (!result.success) {
387
+ logger.error('Deployment failed');
388
+ process.exit(1);
389
+ }
390
+ if (context.options.verbose) {
391
+ logger.info('Deploy command completed successfully');
392
+ }
393
+ }
394
+ catch (error) {
395
+ logger.error('Deploy failed:', error);
396
+ throw error;
397
+ }
398
+ }
399
+ };
400
+ /**
401
+ * Setup deployment options from CLI arguments
402
+ */
403
+ async function setupDeploymentOptions(bytecode, abi, options, config, logger) {
404
+ const deploymentOptions = {
405
+ bytecode,
406
+ network: config.target,
407
+ maxDataSize: parseInt(options.maxDataSize),
408
+ computeBudget: parseInt(options.computeBudget),
409
+ fiveVMProgramId: options.programId, // Pass the programId
410
+ vmStateAccount: options.vmStateAccount,
411
+ exportMetadata: buildExportMetadataFromAbi(abi),
412
+ namespace: options.namespace,
413
+ namespaceManager: options.namespaceManager,
414
+ };
415
+ return deploymentOptions;
416
+ }
417
+ function canonicalizeNamespace(namespace) {
418
+ const trimmed = namespace.trim();
419
+ const symbol = trimmed[0];
420
+ const allowed = new Set(['!', '@', '#', '$', '%']);
421
+ if (!allowed.has(symbol)) {
422
+ throw new Error('namespace symbol must be one of ! @ # $ %');
423
+ }
424
+ const parts = trimmed.slice(1).split('/');
425
+ if (parts.length !== 2) {
426
+ throw new Error('namespace must be in format @domain/subprogram');
427
+ }
428
+ const normalize = (s) => s.toLowerCase();
429
+ const domain = normalize(parts[0]);
430
+ const subprogram = normalize(parts[1]);
431
+ const valid = (s) => /^[a-z0-9-]+$/.test(s) && s.length > 0;
432
+ if (!valid(domain) || !valid(subprogram)) {
433
+ throw new Error('namespace domain/subprogram must be lowercase alphanumeric + hyphen');
434
+ }
435
+ return { symbol, domain, subprogram, canonical: `${symbol}${domain}/${subprogram}` };
436
+ }
437
+ async function validateNamespaceOwnership(rootDir, namespace, keypairPath) {
438
+ const parsed = canonicalizeNamespace(namespace);
439
+ const lockPath = join(rootDir, 'five.lock');
440
+ let lockDoc = {};
441
+ try {
442
+ const content = await readFile(lockPath, 'utf8');
443
+ lockDoc = parseToml(content);
444
+ }
445
+ catch {
446
+ throw new Error(`namespace ownership check failed: missing ${lockPath}`);
447
+ }
448
+ const tlds = Array.isArray(lockDoc.namespace_tlds) ? lockDoc.namespace_tlds : [];
449
+ const tld = tlds.find((entry) => entry?.symbol === parsed.symbol && entry?.domain === parsed.domain);
450
+ if (!tld) {
451
+ throw new Error(`namespace ${parsed.symbol}${parsed.domain} is not registered in local lockfile`);
452
+ }
453
+ const expanded = keypairPath.startsWith('~/')
454
+ ? keypairPath.replace('~', process.env.HOME || '')
455
+ : keypairPath;
456
+ const secret = JSON.parse(await readFile(expanded, 'utf8'));
457
+ const owner = Keypair.fromSecretKey(Uint8Array.from(secret)).publicKey.toBase58();
458
+ if (tld.owner !== owner) {
459
+ throw new Error(`namespace ownership mismatch: owner is ${tld.owner}, deployer is ${owner}`);
460
+ }
461
+ }
462
+ export function buildExportMetadataFromAbi(abi) {
463
+ const methods = [];
464
+ const interfaces = [];
465
+ if (!abi) {
466
+ return { methods, interfaces };
467
+ }
468
+ if (Array.isArray(abi.functions)) {
469
+ for (const fn of abi.functions) {
470
+ if (!fn || typeof fn.name !== 'string')
471
+ continue;
472
+ const isPublic = fn.is_public === true || fn.visibility === 'public';
473
+ if (isPublic)
474
+ methods.push(fn.name);
475
+ }
476
+ }
477
+ else if (abi.functions && typeof abi.functions === 'object') {
478
+ for (const name of Object.keys(abi.functions)) {
479
+ methods.push(name);
480
+ }
481
+ }
482
+ return { methods, interfaces };
483
+ }
484
+ export async function updateLockfileExports(rootDir, packageName, address, bytecode, exportMetadata) {
485
+ const lockPath = join(rootDir, 'five.lock');
486
+ let lockDoc = { version: 1, packages: [] };
487
+ try {
488
+ const content = await readFile(lockPath, 'utf8');
489
+ lockDoc = parseToml(content);
490
+ }
491
+ catch {
492
+ // No lockfile yet; create one.
493
+ }
494
+ if (!Array.isArray(lockDoc.packages)) {
495
+ lockDoc.packages = [];
496
+ }
497
+ const exportsPayload = {
498
+ methods: exportMetadata?.methods || [],
499
+ interfaces: (exportMetadata?.interfaces || []).map((iface) => ({
500
+ name: iface.name,
501
+ method_map: iface.methodMap || {},
502
+ })),
503
+ };
504
+ const entry = {
505
+ name: packageName,
506
+ version: '0.0.0',
507
+ address,
508
+ bytecode_hash: computeHash(bytecode),
509
+ deployed_at: new Date().toISOString(),
510
+ exports: exportsPayload,
511
+ };
512
+ const existingIndex = lockDoc.packages.findIndex((p) => p && (p.name === packageName || p.address === address));
513
+ if (existingIndex >= 0) {
514
+ lockDoc.packages[existingIndex] = {
515
+ ...lockDoc.packages[existingIndex],
516
+ ...entry,
517
+ };
518
+ }
519
+ else {
520
+ lockDoc.packages.push(entry);
521
+ }
522
+ await writeFile(lockPath, stringifyToml(lockDoc), 'utf8');
523
+ }
524
+ export async function updateLockfileNamespace(rootDir, namespace, address) {
525
+ const lockPath = join(rootDir, 'five.lock');
526
+ let lockDoc = { version: 1, packages: [], namespaces: [] };
527
+ try {
528
+ const content = await readFile(lockPath, 'utf8');
529
+ lockDoc = parseToml(content);
530
+ }
531
+ catch {
532
+ // No lockfile yet; create one.
533
+ }
534
+ if (!Array.isArray(lockDoc.namespaces)) {
535
+ lockDoc.namespaces = [];
536
+ }
537
+ const idx = lockDoc.namespaces.findIndex((item) => item && item.namespace === namespace);
538
+ const value = {
539
+ namespace,
540
+ address,
541
+ updated_at: new Date().toISOString(),
542
+ };
543
+ if (idx >= 0) {
544
+ lockDoc.namespaces[idx] = value;
545
+ }
546
+ else {
547
+ lockDoc.namespaces.push(value);
548
+ }
549
+ await writeFile(lockPath, stringifyToml(lockDoc), 'utf8');
550
+ }
551
+ async function resolveNamespaceManagerScript(rootDir, projectManager, cliOverride) {
552
+ if (cliOverride)
553
+ return cliOverride;
554
+ if (projectManager)
555
+ return projectManager;
556
+ if (process.env.FIVE_NAMESPACE_MANAGER)
557
+ return process.env.FIVE_NAMESPACE_MANAGER;
558
+ const lockPath = join(rootDir, 'five.lock');
559
+ try {
560
+ const content = await readFile(lockPath, 'utf8');
561
+ const lockDoc = parseToml(content);
562
+ return lockDoc?.namespace_manager?.script_account;
563
+ }
564
+ catch {
565
+ return undefined;
566
+ }
567
+ }
568
+ async function updateLockfileNamespaceManager(rootDir, scriptAccount) {
569
+ const lockPath = join(rootDir, 'five.lock');
570
+ let lockDoc = { version: 1, packages: [], namespaces: [] };
571
+ try {
572
+ const content = await readFile(lockPath, 'utf8');
573
+ lockDoc = parseToml(content);
574
+ }
575
+ catch {
576
+ // No lockfile yet; create one.
577
+ }
578
+ lockDoc.namespace_manager = {
579
+ script_account: scriptAccount,
580
+ updated_at: new Date().toISOString(),
581
+ };
582
+ await writeFile(lockPath, stringifyToml(lockDoc), 'utf8');
583
+ }
584
+ /**
585
+ * Execute actual deployment to Solana network
586
+ */
587
+ async function executeDeployment(deploymentOptions, options, context, config) {
588
+ const { logger } = context;
589
+ if (context.options.debug) {
590
+ logger.debug(`deployment options: ${JSON.stringify(deploymentOptions)}`);
591
+ logger.debug(`options: ${JSON.stringify(options)}`);
592
+ logger.debug(`config: ${JSON.stringify(config)}`);
593
+ }
594
+ const targetPrefix = ConfigManager.getTargetPrefix(config.target);
595
+ if (context.options.verbose) {
596
+ logger.info(`${targetPrefix} Deploying to ${deploymentOptions.network}...`);
597
+ }
598
+ try {
599
+ // Get network RPC endpoint from config
600
+ const rpcUrl = config.networks[config.target].rpcUrl;
601
+ if (context.options.debug) {
602
+ logger.debug(`rpc url: ${rpcUrl}`);
603
+ }
604
+ const connection = new Connection(rpcUrl, 'confirmed');
605
+ // Load deployer keypair from config
606
+ const deployerKeypair = await loadKeypair(config.keypairPath, logger);
607
+ if (context.options.debug) {
608
+ logger.debug(`deployer: ${deployerKeypair.publicKey.toString()}`);
609
+ }
610
+ // Deploy using Five SDK
611
+ const spinner = isTTY() ? ora('Deploying via Five SDK...').start() : null;
612
+ // Auto-detect large programs and use appropriate deployment method
613
+ const bytecodeArray = new Uint8Array(deploymentOptions.bytecode);
614
+ const isLargeProgram = bytecodeArray.length > 800 || options.forceChunked;
615
+ if (isLargeProgram) {
616
+ if (options.optimized) {
617
+ if (spinner) {
618
+ spinner.text = 'Deploying large program via optimized chunked deployment...';
619
+ }
620
+ }
621
+ else {
622
+ if (spinner) {
623
+ spinner.text = 'Deploying large program via chunked deployment...';
624
+ }
625
+ }
626
+ }
627
+ let result;
628
+ if (isLargeProgram) {
629
+ if (options.optimized) {
630
+ // Use the new optimized deployment method
631
+ result = await FiveSDK.deployLargeProgramOptimizedToSolana(bytecodeArray, connection, deployerKeypair, {
632
+ debug: options.debug || false,
633
+ network: deploymentOptions.network,
634
+ fiveVMProgramId: deploymentOptions.fiveVMProgramId,
635
+ // vmStateAccount: deploymentOptions.vmStateAccount, // Optimized method does not support this yet
636
+ maxRetries: 3,
637
+ chunkSize: options.chunkSize || 950, // Higher default for optimized method
638
+ progressCallback: options.progress ? (transaction, total) => {
639
+ if (spinner) {
640
+ spinner.text = `Optimized deployment: transaction ${transaction}/${total}...`;
641
+ }
642
+ } : undefined
643
+ });
644
+ }
645
+ else {
646
+ // Use traditional large program deployment
647
+ result = await FiveSDK.deployLargeProgramToSolana(bytecodeArray, connection, deployerKeypair, {
648
+ debug: options.debug || false,
649
+ network: deploymentOptions.network,
650
+ fiveVMProgramId: deploymentOptions.fiveVMProgramId,
651
+ vmStateAccount: deploymentOptions.vmStateAccount,
652
+ maxRetries: 3,
653
+ chunkSize: options.chunkSize || 750,
654
+ progressCallback: options.progress ? (chunk, total) => {
655
+ if (spinner) {
656
+ spinner.text = `Deploying chunk ${chunk}/${total}...`;
657
+ }
658
+ } : undefined
659
+ });
660
+ }
661
+ }
662
+ else {
663
+ // Use regular deployment for small programs
664
+ result = await FiveSDK.deployToSolana(bytecodeArray, connection, deployerKeypair, {
665
+ debug: options.debug || false,
666
+ network: deploymentOptions.network,
667
+ computeBudget: deploymentOptions.computeBudget,
668
+ fiveVMProgramId: deploymentOptions.fiveVMProgramId, // Pass programId
669
+ exportMetadata: deploymentOptions.exportMetadata,
670
+ maxRetries: 3
671
+ });
672
+ }
673
+ if (result.success) {
674
+ if (isLargeProgram && 'chunksUsed' in result && result.chunksUsed) {
675
+ const largeResult = result; // Type assertion for large deployment result
676
+ if (options.optimized && 'optimizationSavings' in result) {
677
+ const optimizedResult = result;
678
+ const savingsPercent = Math.round(optimizedResult.optimizationSavings.transactionsSaved / (optimizedResult.optimizationSavings.transactionsSaved + optimizedResult.totalTransactions) * 100);
679
+ if (spinner) {
680
+ spinner.succeed(`Optimized deployment completed (${largeResult.chunksUsed} chunks, ${largeResult.totalTransactions} transactions, ${savingsPercent}% reduction)`);
681
+ }
682
+ }
683
+ else {
684
+ if (spinner) {
685
+ spinner.succeed(`Large program deployment completed (${largeResult.chunksUsed} chunks, ${largeResult.totalTransactions} transactions)`);
686
+ }
687
+ }
688
+ }
689
+ else {
690
+ if (spinner) {
691
+ spinner.succeed('Deployment completed');
692
+ }
693
+ }
694
+ }
695
+ else {
696
+ if (spinner) {
697
+ spinner.fail('Deployment failed');
698
+ }
699
+ }
700
+ if (context.options.debug) {
701
+ logger.debug(`deploy result: ${JSON.stringify(result, null, 2)}`);
702
+ }
703
+ return result;
704
+ }
705
+ catch (error) {
706
+ logger.error('Deployment failed:', error);
707
+ const errorResult = {
708
+ success: false,
709
+ error: error instanceof Error ? error.message : 'Unknown deployment error',
710
+ logs: []
711
+ };
712
+ if (context.options.debug) {
713
+ logger.debug(`deploy error result: ${JSON.stringify(errorResult, null, 2)}`);
714
+ }
715
+ return errorResult;
716
+ }
717
+ }
718
+ /**
719
+ * Simulate deployment without executing
720
+ */
721
+ async function simulateDeployment(deploymentOptions, options, context) {
722
+ const { logger } = context;
723
+ if (context.options.verbose) {
724
+ logger.info('Simulating deployment...');
725
+ }
726
+ // Simulate validation and cost calculation
727
+ await new Promise(resolve => setTimeout(resolve, 1000));
728
+ const estimatedCost = Math.ceil(deploymentOptions.bytecode.length / 1000) * 1000000; // Rough estimate
729
+ return {
730
+ success: true,
731
+ programId: 'SIMULATED_PROGRAM_ID_' + Date.now(),
732
+ transactionId: 'SIMULATED_TX_' + Date.now(),
733
+ deploymentCost: estimatedCost,
734
+ logs: [
735
+ 'Deployment simulation completed',
736
+ `Estimated cost: ${estimatedCost / 1e9} SOL`,
737
+ `Bytecode size: ${deploymentOptions.bytecode.length} bytes`,
738
+ `Target network: ${deploymentOptions.network}`
739
+ ]
740
+ };
741
+ }
742
+ /**
743
+ * Display deployment result in specified format
744
+ */
745
+ function displayDeploymentResult(result, options, logger) {
746
+ if (options.format === 'json') {
747
+ console.log(JSON.stringify(result, null, 2));
748
+ return;
749
+ }
750
+ console.log('\n' + section('Deployment'));
751
+ if (result.success) {
752
+ console.log(uiSuccess('Deployment succeeded'));
753
+ if (result.programId) {
754
+ console.log(keyValue('Program', result.programId));
755
+ }
756
+ if (result.transactionId) {
757
+ console.log(keyValue('Transaction', result.transactionId));
758
+ }
759
+ if (result.deploymentCost !== undefined) {
760
+ const costSOL = result.deploymentCost / 1e9;
761
+ console.log(keyValue('Cost', `${costSOL.toFixed(6)} SOL`));
762
+ }
763
+ if (result.logs && result.logs.length > 0 && options.verbose) {
764
+ console.log(section('Logs'));
765
+ result.logs.forEach((log) => {
766
+ console.log(` ${log}`);
767
+ });
768
+ }
769
+ }
770
+ else {
771
+ console.log(uiError(result.error || 'Deployment failed'));
772
+ }
773
+ }
774
+ /**
775
+ * Get RPC URL for network
776
+ */
777
+ function getNetworkRpcUrl(network) {
778
+ const endpoints = {
779
+ 'devnet': 'https://api.devnet.solana.com',
780
+ 'testnet': 'https://api.testnet.solana.com',
781
+ 'mainnet': 'https://api.mainnet-beta.solana.com',
782
+ 'local': 'http://127.0.0.1:8899'
783
+ };
784
+ return endpoints[network] || endpoints['devnet'];
785
+ }
786
+ /**
787
+ * Load Solana keypair from file
788
+ */
789
+ async function loadKeypair(keypairPath, logger) {
790
+ // Expand tilde in path
791
+ const path = keypairPath.startsWith('~/')
792
+ ? keypairPath.replace('~', process.env.HOME || '')
793
+ : keypairPath;
794
+ try {
795
+ const keypairData = await readFile(path, 'utf8');
796
+ const secretKey = Uint8Array.from(JSON.parse(keypairData));
797
+ const keypair = Keypair.fromSecretKey(secretKey);
798
+ logger.debug(`Loaded keypair from: ${path}`);
799
+ logger.debug(`Public key: ${keypair.publicKey.toString()}`);
800
+ return keypair;
801
+ }
802
+ catch (error) {
803
+ throw new Error(`Failed to load keypair from ${path}: ${error}`);
804
+ }
805
+ }
806
+ //# sourceMappingURL=deploy.js.map