@twin.org/move-to-json 0.0.2-next.9 → 0.0.3-next.10
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 +8 -374
- package/bin/index.js +1 -1
- package/dist/es/cli.js +39 -0
- package/dist/es/cli.js.map +1 -0
- package/dist/es/commands/build.js +220 -0
- package/dist/es/commands/build.js.map +1 -0
- package/dist/{esm/index.mjs → es/commands/deploy.js} +103 -636
- package/dist/es/commands/deploy.js.map +1 -0
- package/dist/es/index.js +10 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/INetworkConfig.js +2 -0
- package/dist/es/models/INetworkConfig.js.map +1 -0
- package/dist/es/utils/buildArtifactUtils.js +28 -0
- package/dist/es/utils/buildArtifactUtils.js.map +1 -0
- package/dist/es/utils/envSetup.js +56 -0
- package/dist/es/utils/envSetup.js.map +1 -0
- package/dist/es/utils/environmentUtils.js +239 -0
- package/dist/es/utils/environmentUtils.js.map +1 -0
- package/dist/es/utils/iotaUtils.js +19 -0
- package/dist/es/utils/iotaUtils.js.map +1 -0
- package/dist/es/utils/moveToJsonUtils.js +27 -0
- package/dist/es/utils/moveToJsonUtils.js.map +1 -0
- package/dist/locales/en.json +78 -94
- package/dist/types/index.d.ts +7 -7
- package/docs/changelog.md +259 -43
- package/docs/reference/classes/CLI.md +2 -2
- package/docs/reference/functions/getDeploymentSeed.md +2 -2
- package/docs/reference/interfaces/INetworkConfig.md +9 -9
- package/docs/usage.md +170 -0
- package/locales/en.json +18 -54
- package/package.json +27 -13
- package/dist/cjs/index.cjs +0 -1481
- package/docs/examples.md +0 -98
package/README.md
CHANGED
|
@@ -1,387 +1,21 @@
|
|
|
1
1
|
# TWIN Move to JSON
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
### IOTA CLI Installation
|
|
8
|
-
|
|
9
|
-
Before using this tool, you need to install the IOTA CLI. The simplest way is using Homebrew:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Install IOTA CLI using Homebrew (macOS/Linux/WSL)
|
|
13
|
-
brew install iotaledger/tap/iota
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
**Alternative installation methods:**
|
|
17
|
-
|
|
18
|
-
For other platforms or installation from source, see the [official IOTA installation guide](https://docs.iota.org/developer/getting-started/install-iota).
|
|
19
|
-
|
|
20
|
-
**Verify installation:**
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
iota --version
|
|
24
|
-
```
|
|
3
|
+
This app provides a command line workflow for compiling Move contracts and producing deployment-ready JSON for IOTA networks. It is intended for development teams that want predictable build and deployment inputs across test and production environments.
|
|
25
4
|
|
|
26
5
|
## Installation
|
|
27
6
|
|
|
28
7
|
```shell
|
|
29
|
-
npm install -
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Quick Start
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# Build contracts for mainnet using environment variables (recommended)
|
|
36
|
-
npx move-to-json build "src/**/*.move" --load-env configs/mainnet.env --output smart-contract-deployments.json
|
|
37
|
-
|
|
38
|
-
# Deploy to mainnet using environment variables (recommended)
|
|
39
|
-
npx move-to-json deploy --load-env configs/mainnet.env --contracts smart-contract-deployments.json
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Environment Setup for Production Deployments
|
|
43
|
-
|
|
44
|
-
### Required Environment Variables
|
|
45
|
-
|
|
46
|
-
For production deployments, you need to set up environment variables containing your deployment credentials. The tool uses network-specific configuration files located in the `configs/` directory.
|
|
47
|
-
|
|
48
|
-
#### 1. Create Environment Files
|
|
49
|
-
|
|
50
|
-
Copy the environment templates for each network you plan to use:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Copy environment templates for each network
|
|
54
|
-
cp configs/testnet.env.example configs/testnet.env
|
|
55
|
-
cp configs/devnet.env.example configs/devnet.env
|
|
56
|
-
cp configs/mainnet.env.example configs/mainnet.env
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
**Note:** Only copy the environment files for the networks you actually plan to deploy to. For development, you typically only need `testnet.env` and `devnet.env`.
|
|
60
|
-
|
|
61
|
-
#### 2. Generate Deployment Mnemonics
|
|
62
|
-
|
|
63
|
-
Use the wallet CLI to generate secure mnemonics and then manually update the appropriate network configuration files with the generated mnemonic.
|
|
64
|
-
|
|
65
|
-
### Environment Variable Reference
|
|
66
|
-
|
|
67
|
-
Each network has its own configuration file in the `configs/` directory with the required deployment mnemonic:
|
|
68
|
-
|
|
69
|
-
| Network | File | Required Variable |
|
|
70
|
-
| ------- | --------------------- | ------------------- |
|
|
71
|
-
| Testnet | `configs/testnet.env` | `DEPLOYER_MNEMONIC` |
|
|
72
|
-
| Devnet | `configs/devnet.env` | `DEPLOYER_MNEMONIC` |
|
|
73
|
-
| Mainnet | `configs/mainnet.env` | `DEPLOYER_MNEMONIC` |
|
|
74
|
-
|
|
75
|
-
### Security Best Practices
|
|
76
|
-
|
|
77
|
-
#### Local Development
|
|
78
|
-
|
|
79
|
-
- Store credentials in `configs/*.env` files (never commit to git - only commit `.example` files)
|
|
80
|
-
- Use test mnemonics with faucet funds for testnet/devnet
|
|
81
|
-
- Use dedicated deployment wallets separate from personal wallets
|
|
82
|
-
|
|
83
|
-
#### CI/CD and Production
|
|
84
|
-
|
|
85
|
-
- Store credentials in GitHub Secrets or your CI/CD secrets manager
|
|
86
|
-
- Use hardware wallets or HSM for mainnet deployment keys
|
|
87
|
-
- Implement approval workflows for mainnet deployments
|
|
88
|
-
- Monitor wallet balances and deployment costs
|
|
89
|
-
|
|
90
|
-
### Generating Credentials
|
|
91
|
-
|
|
92
|
-
#### 1. Generate Mnemonic and Seed
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
# Generate a new 24-word mnemonic and save to wallet.env
|
|
96
|
-
npx "@twin.org/wallet-cli" mnemonic --env wallet.env
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
#### 2. Generate Addresses from Seed
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
# Generate 5 addresses and save to address.env
|
|
103
|
-
npx "@twin.org/wallet-cli" address --load-env wallet.env --seed '!SEED' --count 5 --env address.env
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Commands
|
|
107
|
-
|
|
108
|
-
### build
|
|
109
|
-
|
|
110
|
-
Compile Move contracts for a specific network:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
# Using environment variables (recommended)
|
|
114
|
-
npx move-to-json build "src/**/*.move" --load-env configs/<network>.env [--output <file>]
|
|
115
|
-
|
|
116
|
-
# Using explicit network flag
|
|
117
|
-
npx move-to-json build "src/**/*.move" --network <network> [--output <file>]
|
|
118
|
-
|
|
119
|
-
# If installed globally
|
|
120
|
-
move-to-json build "src/**/*.move" --load-env configs/<network>.env [--output <file>]
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Options:**
|
|
124
|
-
|
|
125
|
-
- `--network <network>` - Target network (testnet/devnet/mainnet) **[Optional if using --load-env]**
|
|
126
|
-
- `--load-env <file>` - Load environment variables from file (must contain NETWORK variable)
|
|
127
|
-
- `--output <file>` - Output JSON file (default: smart-contract-deployments.json)
|
|
128
|
-
|
|
129
|
-
**What it does:**
|
|
130
|
-
|
|
131
|
-
1. Reads network from `--network` flag or `NETWORK` environment variable
|
|
132
|
-
2. Validates environment variables for the target network
|
|
133
|
-
3. Cleans build artifacts and Move.lock files
|
|
134
|
-
4. Compiles contracts using unified Move.toml (same bytecode for all networks)
|
|
135
|
-
5. Generates network-aware JSON with package IDs and base64 modules
|
|
136
|
-
|
|
137
|
-
**Key Changes:**
|
|
138
|
-
|
|
139
|
-
- **Unified Move.toml**: Uses single Move.toml file with `framework/testnet` for consistent builds
|
|
140
|
-
- **Network-agnostic compilation**: Same bytecode works across all networks
|
|
141
|
-
- **Environment-aware**: Network targeting handled by IOTA CLI environments, not Move.toml
|
|
142
|
-
|
|
143
|
-
**Example:**
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
# Build for testnet using environment variables (recommended)
|
|
147
|
-
npx move-to-json build "tests/fixtures/sources/**/*.move" --load-env configs/testnet.env --output tests/fixtures/smart-contract-deployments/smart-contract-deployments.json
|
|
148
|
-
|
|
149
|
-
# Build for mainnet using environment variables (recommended)
|
|
150
|
-
npx move-to-json build "src/contracts/**/*.move" --load-env configs/mainnet.env --output smart-contract-deployments.json
|
|
151
|
-
|
|
152
|
-
# Alternative: using explicit network flag
|
|
153
|
-
npx move-to-json build "tests/fixtures/sources/**/*.move" --network testnet --output tests/fixtures/smart-contract-deployments/smart-contract-deployments.json
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### deploy
|
|
157
|
-
|
|
158
|
-
Deploy compiled contracts to the specified network:
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
# Using environment variables (recommended)
|
|
162
|
-
npx move-to-json deploy --load-env configs/<network>.env [--contracts <file>] [options]
|
|
163
|
-
|
|
164
|
-
# Using explicit network flag
|
|
165
|
-
npx move-to-json deploy --network <network> [--load-env configs/<network>.env] [options]
|
|
166
|
-
|
|
167
|
-
# If installed globally
|
|
168
|
-
move-to-json deploy --load-env configs/<network>.env [--contracts <file>] [options]
|
|
8
|
+
npm install -D @twin.org/move-to-json
|
|
169
9
|
```
|
|
170
10
|
|
|
171
|
-
|
|
11
|
+
## Usage
|
|
172
12
|
|
|
173
|
-
|
|
174
|
-
- `--load-env <file>` - Load environment variables from file (must contain NETWORK variable)
|
|
175
|
-
- `--contracts <file>` - Compiled modules JSON file (default: smart-contract-deployments.json)
|
|
176
|
-
- `--dry-run` - Simulate deployment without executing
|
|
177
|
-
- `--force` - Force redeployment of existing packages
|
|
13
|
+
Usage of the CLI is shown in the examples [docs/usage.md](docs/usage.md)
|
|
178
14
|
|
|
179
|
-
|
|
15
|
+
## Reference
|
|
180
16
|
|
|
181
|
-
|
|
182
|
-
2. Loads environment variables from the file specified via `--load-env` flag
|
|
183
|
-
3. Validates deployment credentials are available
|
|
184
|
-
4. Checks wallet balance against gas requirements
|
|
185
|
-
5. Loads compiled contracts from JSON
|
|
186
|
-
6. Deploys using IOTA CLI with active network environment
|
|
187
|
-
7. Extracts and saves both Package ID and UpgradeCap ID
|
|
188
|
-
8. Updates JSON with deployed package information
|
|
17
|
+
Detailed reference documentation for the API can be found in [docs/reference/index.md](docs/reference/index.md)
|
|
189
18
|
|
|
190
|
-
|
|
19
|
+
## Changelog
|
|
191
20
|
|
|
192
|
-
|
|
193
|
-
- **Network targeting**: Uses IOTA CLI environments instead of Move.toml configuration
|
|
194
|
-
- **Consistent deployment**: Same compiled bytecode deployed to all networks
|
|
195
|
-
- **UpgradeCap tracking**: Captures and stores UpgradeCap object ID for future package upgrades
|
|
196
|
-
|
|
197
|
-
**Prerequisites:**
|
|
198
|
-
Ensure IOTA CLI environments are configured:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
# Check available environments
|
|
202
|
-
iota client envs
|
|
203
|
-
|
|
204
|
-
# Configure networks if missing
|
|
205
|
-
iota client new-env --alias testnet --rpc https://fullnode.testnet.iota.cafe:443
|
|
206
|
-
iota client new-env --alias mainnet --rpc https://api.mainnet.iota.cafe
|
|
207
|
-
iota client new-env --alias devnet --rpc https://api.devnet.iota.cafe
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**Example:**
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
# Deploy to testnet using environment variables (recommended)
|
|
214
|
-
npx move-to-json deploy --load-env configs/testnet.env --contracts tests/fixtures/smart-contract-deployments/smart-contract-deployments.json
|
|
215
|
-
|
|
216
|
-
# Deploy to mainnet using environment variables (recommended)
|
|
217
|
-
npx move-to-json deploy --load-env configs/mainnet.env --contracts tests/fixtures/smart-contract-deployments/smart-contract-deployments.json
|
|
218
|
-
|
|
219
|
-
# Dry run (simulation)
|
|
220
|
-
npx move-to-json deploy --load-env configs/testnet.env --contracts tests/fixtures/smart-contract-deployments/smart-contract-deployments.json --dry-run
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
**Output:**
|
|
224
|
-
The deployment saves both Package ID and UpgradeCap ID:
|
|
225
|
-
|
|
226
|
-
```json
|
|
227
|
-
{
|
|
228
|
-
"testnet": {
|
|
229
|
-
"packageId": "0x...",
|
|
230
|
-
"package": "base64data...",
|
|
231
|
-
"deployedPackageId": "0x239ad3ea39f0910a4dc4c98161bcde948fb5ed0d7d7ae6d9a593239c43af748e",
|
|
232
|
-
"upgradeCap": "0xfd6269c28e3931e41aa9d9e08ffabb8162cf1fd0baaef14094b4442e6c743edf"
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Important Note on UpgradeCap:**
|
|
238
|
-
The UpgradeCap ID is crucial for package upgrades. Keep this secure - it's required to upgrade your deployed packages in the future.
|
|
239
|
-
|
|
240
|
-
## Unified Move.toml Approach
|
|
241
|
-
|
|
242
|
-
The tool now uses a **unified Move.toml approach** that eliminates the need for network-specific Move.toml files. This provides better consistency and reduces complexity.
|
|
243
|
-
|
|
244
|
-
### Single Move.toml Configuration
|
|
245
|
-
|
|
246
|
-
Instead of multiple `Move.toml.{network}` files, use a single `Move.toml` file:
|
|
247
|
-
|
|
248
|
-
```toml
|
|
249
|
-
[package]
|
|
250
|
-
name = "my_contract"
|
|
251
|
-
version = "0.0.1"
|
|
252
|
-
edition = "2024.beta"
|
|
253
|
-
|
|
254
|
-
[dependencies]
|
|
255
|
-
Iota = {
|
|
256
|
-
git = "https://github.com/iotaledger/iota.git",
|
|
257
|
-
subdir = "crates/iota-framework/packages/iota-framework",
|
|
258
|
-
rev = "framework/testnet"
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
[addresses]
|
|
262
|
-
my_contract = "0x0"
|
|
263
|
-
|
|
264
|
-
[dev-dependencies]
|
|
265
|
-
# Optional: Override dependencies for testing
|
|
266
|
-
# Iota = { git = "https://github.com/iotaledger/iota.git", subdir = "crates/iota-framework/packages/iota-framework", rev = "framework/devnet", override = true }
|
|
267
|
-
|
|
268
|
-
[dev-addresses]
|
|
269
|
-
# Optional: Override addresses for development
|
|
270
|
-
# my_contract = "0x1234"
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Framework Version Strategy
|
|
274
|
-
|
|
275
|
-
- **Development/Testing**: Use `framework/testnet` for all networks
|
|
276
|
-
- **Production**: Same `framework/testnet` works for mainnet deployment
|
|
277
|
-
- **Consistency**: Same bytecode compiles for all target networks
|
|
278
|
-
|
|
279
|
-
### Network Targeting
|
|
280
|
-
|
|
281
|
-
Network targeting is handled through **IOTA CLI environments**, not Move.toml:
|
|
282
|
-
|
|
283
|
-
```bash
|
|
284
|
-
# Build once - same bytecode for all networks
|
|
285
|
-
iota move build
|
|
286
|
-
|
|
287
|
-
# Deploy to different networks by switching environments
|
|
288
|
-
iota client switch --env testnet && iota client publish
|
|
289
|
-
iota client switch --env mainnet && iota client publish
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Benefits
|
|
293
|
-
|
|
294
|
-
- ✅ **Consistent builds** across all networks
|
|
295
|
-
- ✅ **Reduced complexity** - no file copying
|
|
296
|
-
- ✅ **Industry standard** approach
|
|
297
|
-
- ✅ **Better maintainability** - single source of truth
|
|
298
|
-
- ✅ **Eliminated build conflicts** between networks
|
|
299
|
-
|
|
300
|
-
## Package Upgrades and UpgradeCap Management
|
|
301
|
-
|
|
302
|
-
When you deploy a Move package on IOTA, the network creates an **UpgradeCap** (Upgrade Capability) object that controls the ability to upgrade that package. This tool automatically captures and stores the UpgradeCap ID for future use.
|
|
303
|
-
|
|
304
|
-
### What is an UpgradeCap?
|
|
305
|
-
|
|
306
|
-
The UpgradeCap is a special object that:
|
|
307
|
-
|
|
308
|
-
- **Controls package upgrades**: Only the holder can upgrade the package
|
|
309
|
-
- **Is created once**: Generated during initial package deployment
|
|
310
|
-
- **Must be preserved**: Lost UpgradeCap = no future upgrades possible
|
|
311
|
-
- **Is network-specific**: Each network deployment gets its own UpgradeCap
|
|
312
|
-
|
|
313
|
-
### UpgradeCap Storage
|
|
314
|
-
|
|
315
|
-
The tool stores UpgradeCap IDs in the smart-contract-deployments.json file:
|
|
316
|
-
|
|
317
|
-
```json
|
|
318
|
-
{
|
|
319
|
-
"testnet": {
|
|
320
|
-
"packageId": "0xabc123...",
|
|
321
|
-
"package": "base64data...",
|
|
322
|
-
"deployedPackageId": "0x239ad3ea39f0910a4dc4c98161bcde948fb5ed0d7d7ae6d9a593239c43af748e",
|
|
323
|
-
"upgradeCap": "0xfd6269c28e3931e41aa9d9e08ffabb8162cf1fd0baaef14094b4442e6c743edf"
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### Using UpgradeCap for Package Upgrades
|
|
329
|
-
|
|
330
|
-
To upgrade a deployed package, you'll need the UpgradeCap ID:
|
|
331
|
-
|
|
332
|
-
```bash
|
|
333
|
-
# Example upgrade command (using IOTA CLI)
|
|
334
|
-
iota client call --package 0x2 --module package --function upgrade \
|
|
335
|
-
--args 0xfd6269c28e3931e41aa9d9e08ffabb8162cf1fd0baaef14094b4442e6c743edf \
|
|
336
|
-
--gas-budget 50000000
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## Known Issues
|
|
340
|
-
|
|
341
|
-
### Windows
|
|
342
|
-
|
|
343
|
-
#### Long Path Limitation Error
|
|
344
|
-
|
|
345
|
-
**Error Symptoms:**
|
|
346
|
-
|
|
347
|
-
- Build commands fail with `Failed to reset to latest Git state 'mainnet'`
|
|
348
|
-
- Tests timeout during Move compilation
|
|
349
|
-
- Git dependency resolution failures
|
|
350
|
-
|
|
351
|
-
**Root Cause:**
|
|
352
|
-
Windows has a default file path length limitation of 260 characters. The IOTA framework repository contains files with very long paths (especially in test directories) that exceed this limit, causing Git operations to fail during dependency resolution.
|
|
353
|
-
|
|
354
|
-
**Solution:**
|
|
355
|
-
Enable long path support in both Git and Windows:
|
|
356
|
-
|
|
357
|
-
1. **Enable Git Long Paths:**
|
|
358
|
-
|
|
359
|
-
```bash
|
|
360
|
-
git config --global core.longpaths true
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
2. **Enable Windows Long Path Support (requires Administrator privileges):**
|
|
364
|
-
|
|
365
|
-
```powershell
|
|
366
|
-
# Run PowerShell as Administrator
|
|
367
|
-
Set-ItemProperty -Path 'HKLM:SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
3. **Restart your computer** for Windows registry changes to take effect.
|
|
371
|
-
|
|
372
|
-
**Verification:**
|
|
373
|
-
After applying the fix, you should see successful Git dependency updates:
|
|
374
|
-
|
|
375
|
-
```bash
|
|
376
|
-
UPDATING GIT DEPENDENCY https://github.com/iotaledger/iota.git
|
|
377
|
-
INCLUDING DEPENDENCY Iota
|
|
378
|
-
INCLUDING DEPENDENCY MoveStdlib
|
|
379
|
-
BUILDING nft
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
**Alternative Workaround:**
|
|
383
|
-
If you cannot enable long path support system-wide, you can use the `--skip-fetch-latest-git-deps` flag as a workaround, though this will use cached dependencies instead of the latest versions:
|
|
384
|
-
|
|
385
|
-
```bash
|
|
386
|
-
iota move build --skip-fetch-latest-git-deps
|
|
387
|
-
```
|
|
21
|
+
The changes between each version can be found in [docs/changelog.md](docs/changelog.md)
|
package/bin/index.js
CHANGED
package/dist/es/cli.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { CLIBase } from "@twin.org/cli-core";
|
|
6
|
+
import { buildCommandBuild } from "./commands/build.js";
|
|
7
|
+
import { buildCommandDeploy } from "./commands/deploy.js";
|
|
8
|
+
/**
|
|
9
|
+
* The main entry point for the Move to JSON CLI.
|
|
10
|
+
*/
|
|
11
|
+
export class CLI extends CLIBase {
|
|
12
|
+
/**
|
|
13
|
+
* Run the app.
|
|
14
|
+
* @param argv The process arguments.
|
|
15
|
+
* @param localesDirectory The directory for the locales, default to relative to the script.
|
|
16
|
+
* @param options Additional options.
|
|
17
|
+
* @param options.overrideOutputWidth Override the output width.
|
|
18
|
+
* @returns The exit code.
|
|
19
|
+
*/
|
|
20
|
+
async run(argv, localesDirectory, options) {
|
|
21
|
+
return this.execute({
|
|
22
|
+
title: "TWIN Move to JSON",
|
|
23
|
+
appName: "move-to-json",
|
|
24
|
+
version: "0.0.3-next.10", // x-release-please-version
|
|
25
|
+
icon: "⚙️ ",
|
|
26
|
+
supportsEnvFiles: true,
|
|
27
|
+
overrideOutputWidth: options?.overrideOutputWidth
|
|
28
|
+
}, localesDirectory ?? path.join(path.dirname(fileURLToPath(import.meta.url)), "../locales"), argv);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Configure any options or actions at the root program level.
|
|
32
|
+
* @param program The root program command.
|
|
33
|
+
*/
|
|
34
|
+
configureRoot(program) {
|
|
35
|
+
buildCommandBuild(program);
|
|
36
|
+
buildCommandDeploy(program);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;GAEG;AACH,MAAM,OAAO,GAAI,SAAQ,OAAO;IAC/B;;;;;;;OAOG;IACI,KAAK,CAAC,GAAG,CACf,IAAc,EACd,gBAAyB,EACzB,OAEC;QAED,OAAO,IAAI,CAAC,OAAO,CAClB;YACC,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,cAAc;YACvB,OAAO,EAAE,eAAe,EAAE,2BAA2B;YACrD,IAAI,EAAE,KAAK;YACX,gBAAgB,EAAE,IAAI;YACtB,mBAAmB,EAAE,OAAO,EAAE,mBAAmB;SACjD,EACD,gBAAgB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,EACzF,IAAI,CACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACO,aAAa,CAAC,OAAgB;QACvC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC3B,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CLIBase } from \"@twin.org/cli-core\";\nimport type { Command } from \"commander\";\nimport { buildCommandBuild } from \"./commands/build.js\";\nimport { buildCommandDeploy } from \"./commands/deploy.js\";\n\n/**\n * The main entry point for the Move to JSON CLI.\n */\nexport class CLI extends CLIBase {\n\t/**\n\t * Run the app.\n\t * @param argv The process arguments.\n\t * @param localesDirectory The directory for the locales, default to relative to the script.\n\t * @param options Additional options.\n\t * @param options.overrideOutputWidth Override the output width.\n\t * @returns The exit code.\n\t */\n\tpublic async run(\n\t\targv: string[],\n\t\tlocalesDirectory?: string,\n\t\toptions?: {\n\t\t\toverrideOutputWidth?: number;\n\t\t}\n\t): Promise<number> {\n\t\treturn this.execute(\n\t\t\t{\n\t\t\t\ttitle: \"TWIN Move to JSON\",\n\t\t\t\tappName: \"move-to-json\",\n\t\t\t\tversion: \"0.0.3-next.10\", // x-release-please-version\n\t\t\t\ticon: \"⚙️ \",\n\t\t\t\tsupportsEnvFiles: true,\n\t\t\t\toverrideOutputWidth: options?.overrideOutputWidth\n\t\t\t},\n\t\t\tlocalesDirectory ?? path.join(path.dirname(fileURLToPath(import.meta.url)), \"../locales\"),\n\t\t\targv\n\t\t);\n\t}\n\n\t/**\n\t * Configure any options or actions at the root program level.\n\t * @param program The root program command.\n\t */\n\tprotected configureRoot(program: Command): void {\n\t\tbuildCommandBuild(program);\n\t\tbuildCommandDeploy(program);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { promises as fsPromises } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { CLIDisplay, CLIParam, CLIUtils } from "@twin.org/cli-core";
|
|
6
|
+
import { Converter, GeneralError, Guards, I18n, Is, StringHelper } from "@twin.org/core";
|
|
7
|
+
import { Sha3 } from "@twin.org/crypto";
|
|
8
|
+
import { NetworkTypes } from "@twin.org/dlt-iota";
|
|
9
|
+
import FastGlob from "fast-glob";
|
|
10
|
+
import { verifyIotaSDK } from "../utils/iotaUtils.js";
|
|
11
|
+
import { searchDirectoryForMoveToml } from "../utils/moveToJsonUtils.js";
|
|
12
|
+
/**
|
|
13
|
+
* Build the build command to be consumed by the CLI.
|
|
14
|
+
* @param program The command to build on.
|
|
15
|
+
*/
|
|
16
|
+
export function buildCommandBuild(program) {
|
|
17
|
+
program
|
|
18
|
+
.command("build")
|
|
19
|
+
.description(I18n.formatMessage("commands.build.description"))
|
|
20
|
+
.argument("<inputGlob>", I18n.formatMessage("commands.build.options.inputGlob.description"))
|
|
21
|
+
.option(I18n.formatMessage("commands.build.options.network.param"), I18n.formatMessage("commands.build.options.network.description"), "!NETWORK")
|
|
22
|
+
.option(I18n.formatMessage("commands.build.options.output.param"), I18n.formatMessage("commands.build.options.output.description"), "smart-contract-deployments.json")
|
|
23
|
+
.action(actionCommandBuild);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Action for the build command.
|
|
27
|
+
* @param inputGlob A glob pattern that matches one or more Move files
|
|
28
|
+
* @param opts Additional options.
|
|
29
|
+
* @param opts.network Target network (testnet/devnet/mainnet).
|
|
30
|
+
* @param opts.output Where we store the final compiled modules.
|
|
31
|
+
*/
|
|
32
|
+
export async function actionCommandBuild(inputGlob, opts) {
|
|
33
|
+
try {
|
|
34
|
+
const networkRaw = CLIParam.stringValue("network", opts.network);
|
|
35
|
+
const network = networkRaw;
|
|
36
|
+
Guards.arrayOneOf("commands", "network", network, Object.values(NetworkTypes));
|
|
37
|
+
// Verify the IOTA SDK before we do anything else
|
|
38
|
+
await verifyIotaSDK();
|
|
39
|
+
const { normalizedGlob, normalizedOutput, executionDir } = normalizePathsAndWorkingDir(inputGlob, opts.output ?? "smart-contract-deployments.json");
|
|
40
|
+
CLIDisplay.section(I18n.formatMessage("commands.build.section.buildingMoveContracts", {
|
|
41
|
+
network
|
|
42
|
+
}));
|
|
43
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.inputGlob"), inputGlob);
|
|
44
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.outputJson"), normalizedOutput);
|
|
45
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.network"), network);
|
|
46
|
+
CLIDisplay.break();
|
|
47
|
+
// Find matching .move files
|
|
48
|
+
CLIDisplay.task(I18n.formatMessage("commands.build.progress.searchingFiles"));
|
|
49
|
+
const matchedFiles = await FastGlob([normalizedGlob, "!**/build/**/*.move", "!**/dependencies/**/*.move"], {
|
|
50
|
+
cwd: executionDir,
|
|
51
|
+
absolute: true,
|
|
52
|
+
dot: true,
|
|
53
|
+
followSymbolicLinks: false,
|
|
54
|
+
caseSensitiveMatch: false, // Important for Windows
|
|
55
|
+
onlyFiles: true,
|
|
56
|
+
stats: false
|
|
57
|
+
});
|
|
58
|
+
if (matchedFiles.length === 0) {
|
|
59
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.warnings.noMoveFilesFound", { inputGlob }), "", 2);
|
|
60
|
+
}
|
|
61
|
+
else if (matchedFiles.length > 1) {
|
|
62
|
+
throw new GeneralError("commands", "commands.build.multipleFilesNotSupported", {
|
|
63
|
+
fileCount: matchedFiles.length,
|
|
64
|
+
fileList: matchedFiles.map(f => path.basename(f)).join(", ")
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.matchedFilesCount"), matchedFiles.length.toString());
|
|
68
|
+
CLIDisplay.break();
|
|
69
|
+
// Prepare build environment
|
|
70
|
+
CLIDisplay.task(I18n.formatMessage("commands.build.progress.preparingBuildEnvironment"));
|
|
71
|
+
// Find all Move projects in the directory tree
|
|
72
|
+
const moveProjects = [];
|
|
73
|
+
await searchDirectoryForMoveToml(executionDir, moveProjects);
|
|
74
|
+
for (const projectRoot of moveProjects) {
|
|
75
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.preparedProject"), projectRoot, 1);
|
|
76
|
+
}
|
|
77
|
+
const existingJson = await CLIUtils.readJsonFile(normalizedOutput);
|
|
78
|
+
const finalJson = existingJson ?? {};
|
|
79
|
+
if (existingJson) {
|
|
80
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.mergingWithExistingJson"), normalizedOutput);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.noExistingJsonFound"), I18n.formatMessage("commands.build.labels.creatingNewJsonStructure"), 1);
|
|
84
|
+
}
|
|
85
|
+
CLIDisplay.break();
|
|
86
|
+
// Process the single Move file (validation ensures exactly 0 or 1 file)
|
|
87
|
+
if (matchedFiles.length === 1) {
|
|
88
|
+
const moveFile = matchedFiles[0];
|
|
89
|
+
CLIDisplay.task(I18n.formatMessage("commands.build.progress.processingMoveFile"), moveFile);
|
|
90
|
+
try {
|
|
91
|
+
const compiled = await processMoveFile(moveFile);
|
|
92
|
+
if (compiled) {
|
|
93
|
+
const { contractName, packageId, packageBytecode } = compiled;
|
|
94
|
+
const contractData = finalJson[network] ?? {
|
|
95
|
+
packageId: "",
|
|
96
|
+
packageBytecode: ""
|
|
97
|
+
};
|
|
98
|
+
// If the packageId changed that means the bytecode changed
|
|
99
|
+
// since the packageId is a hash of the bytecode.
|
|
100
|
+
if (packageId !== contractData.packageId) {
|
|
101
|
+
contractData.packageId = packageId; // Update packageId to new value
|
|
102
|
+
contractData.packageBytecode = packageBytecode; // Update bytecode
|
|
103
|
+
// If deployedPackageId exists, it means this contract was previously deployed.
|
|
104
|
+
// so we need to clear it to signal that a redeployment (upgrade) is needed.
|
|
105
|
+
// but also capture it so that we can perform upgrades
|
|
106
|
+
if (Is.stringValue(contractData.deployedPackageId)) {
|
|
107
|
+
contractData.lastDeployedPackageId = contractData.deployedPackageId;
|
|
108
|
+
// Clear deployedPackageId when bytecode changed, preserve upgrade metadata
|
|
109
|
+
delete contractData.deployedPackageId; // Clear to signal upgrade needed
|
|
110
|
+
}
|
|
111
|
+
finalJson[network] = contractData;
|
|
112
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.bytecodeChanged"), I18n.formatMessage("commands.build.labels.deployedPackageIdCleared"), 2);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.bytecodeUnchanged"), I18n.formatMessage("commands.build.labels.deployedPackageIdPreserved"), 2);
|
|
116
|
+
}
|
|
117
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.updatedNetworkPackage", { network }), contractName, 2);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
throw new GeneralError("commands", "commands.build.contractProcessingFailed", { file: moveFile }, err);
|
|
122
|
+
}
|
|
123
|
+
CLIDisplay.break();
|
|
124
|
+
}
|
|
125
|
+
CLIDisplay.task(I18n.formatMessage("commands.build.progress.writingJsonFile"));
|
|
126
|
+
await CLIUtils.writeJsonFile(normalizedOutput, finalJson, false);
|
|
127
|
+
CLIDisplay.break();
|
|
128
|
+
CLIDisplay.done();
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
CLIDisplay.error(err);
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Process a single Move file by compiling it, computing the packageId, and base64-encoding the .mv modules.
|
|
137
|
+
* @param moveFile The path to a single Move source file.
|
|
138
|
+
* @returns The compiled results or undefined if no modules found.
|
|
139
|
+
*/
|
|
140
|
+
async function processMoveFile(moveFile) {
|
|
141
|
+
// The contract name is based on the .move file's base name in kebab-case
|
|
142
|
+
const { name: baseName } = path.parse(moveFile);
|
|
143
|
+
const contractName = StringHelper.kebabCase(baseName);
|
|
144
|
+
// Find the "project root" (the directory containing Move.toml).
|
|
145
|
+
const projectRoot = getProjectRoot(moveFile);
|
|
146
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.contractName"), contractName, 1);
|
|
147
|
+
// Compile the contract
|
|
148
|
+
try {
|
|
149
|
+
const cliArgs = ["move", "build"];
|
|
150
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.compileCommand"), `iota ${cliArgs.join(" ")}`, 1);
|
|
151
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.workingDirectory"), projectRoot, 1);
|
|
152
|
+
await CLIUtils.runShellApp("iota", cliArgs, projectRoot);
|
|
153
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.compileResult"), I18n.formatMessage("commands.build.labels.buildCompleted"), 1);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new GeneralError("commands", "commands.build.buildFailed", { file: moveFile }, error);
|
|
157
|
+
}
|
|
158
|
+
// Get the bytecode modules
|
|
159
|
+
const buildFolderName = StringHelper.snakeCase(baseName);
|
|
160
|
+
const bytecodeModulesPath = path.join(projectRoot, "build", buildFolderName, "bytecode_modules");
|
|
161
|
+
try {
|
|
162
|
+
await fsPromises.access(bytecodeModulesPath);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.warnings.noBytecodeModulesFolder", { contractName }), "", 2);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const moduleFiles = await fsPromises.readdir(bytecodeModulesPath);
|
|
169
|
+
const mvFiles = moduleFiles.filter(f => f.endsWith(".mv"));
|
|
170
|
+
if (mvFiles.length === 0) {
|
|
171
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.warnings.noMvFilesFound", { contractName }), "", 2);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Compute the package ID
|
|
175
|
+
const modulesBytesForHash = [];
|
|
176
|
+
const modulesBase64 = [];
|
|
177
|
+
for (const file of mvFiles) {
|
|
178
|
+
const modulePath = path.join(bytecodeModulesPath, file);
|
|
179
|
+
const moduleBytes = await fsPromises.readFile(modulePath);
|
|
180
|
+
modulesBytesForHash.push(moduleBytes);
|
|
181
|
+
modulesBase64.push(Converter.bytesToBase64(moduleBytes));
|
|
182
|
+
}
|
|
183
|
+
const concatenated = Buffer.concat(modulesBytesForHash);
|
|
184
|
+
const computedPackageIdBytes = Sha3.sum256(concatenated);
|
|
185
|
+
const computedPackageId = `${Converter.bytesToHex(computedPackageIdBytes, true)}`;
|
|
186
|
+
CLIDisplay.value(I18n.formatMessage("commands.build.labels.computedPackageId"), computedPackageId, 2);
|
|
187
|
+
// If multiple modules, store them as an array
|
|
188
|
+
const packageData = modulesBase64.length === 1 ? modulesBase64[0] : modulesBase64;
|
|
189
|
+
return {
|
|
190
|
+
contractName,
|
|
191
|
+
packageId: computedPackageId,
|
|
192
|
+
packageBytecode: packageData
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get the project root directory from a Move file path.
|
|
197
|
+
* @param moveFilePath The path to a Move source file.
|
|
198
|
+
* @returns The project root directory.
|
|
199
|
+
*/
|
|
200
|
+
function getProjectRoot(moveFilePath) {
|
|
201
|
+
return path.resolve(path.parse(moveFilePath).dir, "..");
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Normalize paths and resolve working directory for cross-platform compatibility.
|
|
205
|
+
* @param inputGlob The input glob pattern
|
|
206
|
+
* @param outputJson The output JSON file path
|
|
207
|
+
* @returns Normalized paths and working directory
|
|
208
|
+
*/
|
|
209
|
+
function normalizePathsAndWorkingDir(inputGlob, outputJson) {
|
|
210
|
+
const executionDir = process.cwd();
|
|
211
|
+
// Improved path normalization with StringHelper
|
|
212
|
+
const normalizedGlob = StringHelper.trimTrailingSlashes(path.resolve(inputGlob).replace(/\\/g, "/"));
|
|
213
|
+
const normalizedOutput = path.resolve(outputJson);
|
|
214
|
+
return {
|
|
215
|
+
normalizedGlob,
|
|
216
|
+
normalizedOutput,
|
|
217
|
+
executionDir
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=build.js.map
|