@airmoney-degn/airmoney-cli 0.23.0 → 0.24.1
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/dist/cli/build.js +109 -0
- package/dist/cli/create.js +212 -9
- package/dist/cli/upload.js +75 -13
- package/dist/config.json +1 -1
- package/dist/index.js +14 -0
- package/dist/service/dapp/DappService.js +6 -0
- package/dist/service/log/LogService.js +6 -6
- package/dist/types.js +2 -0
- package/dist/util/metadata.js +32 -2
- package/dist/util/tarball.js +60 -59
- package/package.json +3 -1
- package/readme.md +28 -6
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildCommand = buildCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const metadata_1 = require("../util/metadata");
|
|
42
|
+
const tarball_1 = require("../util/tarball");
|
|
43
|
+
const LogService_1 = require("../service/log/LogService");
|
|
44
|
+
async function buildCommand({ locationFolder }) {
|
|
45
|
+
const projectPath = locationFolder ? path.resolve(process.cwd(), locationFolder) : process.cwd();
|
|
46
|
+
const meta = await (0, metadata_1.loadMetadata)(projectPath);
|
|
47
|
+
if (!meta) {
|
|
48
|
+
throw new Error('No metadata.json found.');
|
|
49
|
+
}
|
|
50
|
+
// Populate build metadata before building
|
|
51
|
+
(0, LogService_1.log)('Populating build metadata...').white();
|
|
52
|
+
(0, metadata_1.populateBuildMetadata)(meta, projectPath);
|
|
53
|
+
(0, metadata_1.saveMetadata)(meta, projectPath);
|
|
54
|
+
if (meta.type !== 'Native') {
|
|
55
|
+
(0, LogService_1.log)('Build is only required for Native (Rust) applications. For Web apps, use your preferred build tool (e.g., npm run build).').white();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Early validation of required assets
|
|
59
|
+
const requiredAssets = ['dapp-logo.png'];
|
|
60
|
+
for (const asset of requiredAssets) {
|
|
61
|
+
if (!fs.existsSync(path.join(projectPath, asset))) {
|
|
62
|
+
throw new Error(`Missing required asset: ${asset}. Please ensure it exists at the root of your project.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
(0, LogService_1.log)(`Building native application: ${meta.displayName}...`).white();
|
|
66
|
+
try {
|
|
67
|
+
// 1. Run cross build
|
|
68
|
+
(0, LogService_1.log)('Executing cross build for aarch64-unknown-linux-gnu...').white();
|
|
69
|
+
// Ensure we are in the project directory for the exec call
|
|
70
|
+
(0, child_process_1.execSync)('cross build --target aarch64-unknown-linux-gnu --release', {
|
|
71
|
+
cwd: projectPath,
|
|
72
|
+
stdio: 'inherit',
|
|
73
|
+
});
|
|
74
|
+
// 2. Find the binary
|
|
75
|
+
// Using standardized binary name "user_app" to match degn-launcher expectations
|
|
76
|
+
const binName = "user_app";
|
|
77
|
+
const binPath = path.join(projectPath, 'target', 'aarch64-unknown-linux-gnu', 'release', binName);
|
|
78
|
+
if (!fs.existsSync(binPath)) {
|
|
79
|
+
// Fallback: try to find any executable in the release folder if the name doesn't match exactly
|
|
80
|
+
(0, LogService_1.log)(`Binary not found at expected path: ${binPath}`).white();
|
|
81
|
+
throw new Error(`Could not find native binary for app: ${binName}`);
|
|
82
|
+
}
|
|
83
|
+
(0, LogService_1.log)(`Native binary found: ${binPath}`).green();
|
|
84
|
+
// 3. Prepare packaging in a temp directory
|
|
85
|
+
(0, LogService_1.log)('Preparing package...').white();
|
|
86
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'airmoney-build-'));
|
|
87
|
+
try {
|
|
88
|
+
// Copy required files to temp dir
|
|
89
|
+
fs.copyFileSync(path.join(projectPath, 'metadata.json'), path.join(tempDir, 'metadata.json'));
|
|
90
|
+
const logoPath = path.join(projectPath, 'dapp-logo.png');
|
|
91
|
+
if (fs.existsSync(logoPath)) {
|
|
92
|
+
fs.copyFileSync(logoPath, path.join(tempDir, 'dapp-logo.png'));
|
|
93
|
+
}
|
|
94
|
+
// Copy the binary as "user_app" for degn-launcher compatibility
|
|
95
|
+
fs.copyFileSync(binPath, path.join(tempDir, "user_app"));
|
|
96
|
+
// 4. Pack the project
|
|
97
|
+
await (0, tarball_1.packProject)(meta, tempDir);
|
|
98
|
+
(0, LogService_1.log)('Build and packaging complete!').green();
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
// Cleanup temp dir
|
|
102
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
(0, LogService_1.log)(`Build failed: ${err.message}`).red();
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/dist/cli/create.js
CHANGED
|
@@ -32,21 +32,95 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.createCommand = createCommand;
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
39
42
|
const os = __importStar(require("os"));
|
|
43
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
40
44
|
const types_1 = require("../types");
|
|
41
45
|
const child_process_1 = require("child_process");
|
|
42
46
|
const LogService_1 = require("../service/log/LogService");
|
|
47
|
+
const env_1 = require("../util/env");
|
|
48
|
+
const DappService_1 = require("../service/dapp/DappService");
|
|
43
49
|
async function createCommand({ name, template, locationFolder, quiet, }) {
|
|
44
50
|
try {
|
|
51
|
+
const questions = [
|
|
52
|
+
{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'displayName',
|
|
55
|
+
message: 'Display Name:',
|
|
56
|
+
default: name,
|
|
57
|
+
validate: (input) => input.trim() !== '' || 'Display Name is required',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'input',
|
|
61
|
+
name: 'description',
|
|
62
|
+
message: 'Description:',
|
|
63
|
+
validate: (input) => input.trim() !== '' || 'Description is required',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'input',
|
|
67
|
+
name: 'author',
|
|
68
|
+
message: 'Author:',
|
|
69
|
+
validate: (input) => input.trim() !== '' || 'Author is required',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'type',
|
|
74
|
+
message: 'App Type:',
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: 'Web (Standard)', value: 'Web' },
|
|
77
|
+
{ name: 'Native (Rust/Slint) - Experimental', value: 'Native' },
|
|
78
|
+
],
|
|
79
|
+
default: 'Web',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: 'input',
|
|
83
|
+
name: 'version',
|
|
84
|
+
message: 'Version:',
|
|
85
|
+
default: '0.0.1',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
const answers = await inquirer_1.default.prompt(questions);
|
|
45
89
|
const identifier = `com.degn.${name}`;
|
|
46
|
-
(0, LogService_1.log)(`Initializing project: ${name}`).white();
|
|
90
|
+
(0, LogService_1.log)(`Initializing project: ${name} [${answers.type}]`).white();
|
|
91
|
+
// Duplicate Check
|
|
92
|
+
try {
|
|
93
|
+
const { userId, apiKey, network } = (0, env_1.validateCredential)();
|
|
94
|
+
const dappService = new DappService_1.DappService(userId, apiKey, network);
|
|
95
|
+
const isAvailable = await dappService.checkDappName(identifier);
|
|
96
|
+
if (!isAvailable) {
|
|
97
|
+
(0, LogService_1.log)(`\nWARNING: The identifier '${identifier}' is already registered on-chain.`).red();
|
|
98
|
+
(0, LogService_1.log)(`You may not be able to publish this DApp unless you own it.`).red();
|
|
99
|
+
const { proceed } = await inquirer_1.default.prompt([
|
|
100
|
+
{
|
|
101
|
+
type: 'confirm',
|
|
102
|
+
name: 'proceed',
|
|
103
|
+
message: 'Do you want to proceed anyway?',
|
|
104
|
+
default: false,
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
if (!proceed) {
|
|
108
|
+
(0, LogService_1.log)('Aborting project creation.').yellow();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
// If credentials aren't setup, we can't check, so we just proceed with a warning
|
|
115
|
+
(0, LogService_1.log)('Notice: Could not verify identifier availability (credentials not found).').yellow();
|
|
116
|
+
}
|
|
47
117
|
const folderName = locationFolder || name;
|
|
48
118
|
const projectPath = path.join(process.cwd(), folderName);
|
|
49
119
|
if (template) {
|
|
120
|
+
// ... (existing template clone logic)
|
|
121
|
+
// Note: Templates are currently Web-only.
|
|
122
|
+
// If user selected Native but used --template, we might want to warn or handle it.
|
|
123
|
+
// For now, let's assume template flag overrides the scaffolding.
|
|
50
124
|
(0, LogService_1.log)('cloning project').white();
|
|
51
125
|
const tempGitDir = path.join(os.tmpdir(), `airmoney-git-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
|
|
52
126
|
// Clean up if it already exists (shouldn't happen, but just in case)
|
|
@@ -72,8 +146,122 @@ async function createCommand({ name, template, locationFolder, quiet, }) {
|
|
|
72
146
|
}
|
|
73
147
|
else {
|
|
74
148
|
fs.mkdirSync(projectPath, { recursive: true });
|
|
75
|
-
|
|
76
|
-
|
|
149
|
+
if (answers.type === 'Native') {
|
|
150
|
+
// Native Rust/Slint Scaffolding
|
|
151
|
+
// Cargo.toml
|
|
152
|
+
const cargoToml = `[package]
|
|
153
|
+
name = "user_app"
|
|
154
|
+
version = "0.1.0"
|
|
155
|
+
edition = "2021"
|
|
156
|
+
|
|
157
|
+
[dependencies]
|
|
158
|
+
slint = "1.0.0"
|
|
159
|
+
|
|
160
|
+
[workspace]
|
|
161
|
+
`;
|
|
162
|
+
fs.writeFileSync(path.join(projectPath, 'Cargo.toml'), cargoToml, 'utf8');
|
|
163
|
+
// src/main.rs
|
|
164
|
+
const srcDir = path.join(projectPath, 'src');
|
|
165
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
166
|
+
const mainRs = `slint::slint! {
|
|
167
|
+
export component MainWindow inherits Window {
|
|
168
|
+
callback close_app();
|
|
169
|
+
VerticalLayout {
|
|
170
|
+
alignment: center;
|
|
171
|
+
Text {
|
|
172
|
+
text: "Hello from ${name} (Native)";
|
|
173
|
+
color: green;
|
|
174
|
+
horizontal-alignment: center;
|
|
175
|
+
}
|
|
176
|
+
Rectangle {
|
|
177
|
+
height: 50px;
|
|
178
|
+
width: 150px;
|
|
179
|
+
background: red;
|
|
180
|
+
border-radius: 4px;
|
|
181
|
+
TouchArea {
|
|
182
|
+
clicked => { root.close_app(); }
|
|
183
|
+
}
|
|
184
|
+
Text {
|
|
185
|
+
text: "Close App";
|
|
186
|
+
color: white;
|
|
187
|
+
x: (parent.width - self.width) / 2;
|
|
188
|
+
y: (parent.height - self.height) / 2;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fn main() {
|
|
196
|
+
let main_window = MainWindow::new().unwrap();
|
|
197
|
+
main_window.window().set_fullscreen(true);
|
|
198
|
+
|
|
199
|
+
let weak_window = main_window.as_weak();
|
|
200
|
+
main_window.on_close_app(move || {
|
|
201
|
+
// Call degn-launcher to close this app
|
|
202
|
+
// We use a simple TCP connection to avoid adding reqwest dependency for just this
|
|
203
|
+
if let Ok(mut stream) = std::net::TcpStream::connect("127.0.0.1:7070") {
|
|
204
|
+
use std::io::Write;
|
|
205
|
+
let _ = stream.write_all(b"POST /close HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n");
|
|
206
|
+
} else {
|
|
207
|
+
eprintln!("Failed to connect to launcher control server");
|
|
208
|
+
std::process::exit(0);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
main_window.run().unwrap();
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
fs.writeFileSync(path.join(srcDir, 'main.rs'), mainRs, 'utf8');
|
|
216
|
+
// .gitignore
|
|
217
|
+
fs.writeFileSync(path.join(projectPath, '.gitignore'), '/target\n', 'utf8');
|
|
218
|
+
// Cross.toml
|
|
219
|
+
const crossToml = `[target.aarch64-unknown-linux-gnu]
|
|
220
|
+
dockerfile = "./cross/Dockerfile"
|
|
221
|
+
|
|
222
|
+
[target.aarch64-unknown-linux-gnu.env]
|
|
223
|
+
PASSTHROUGH = ["PKG_CONFIG_ALLOW_CROSS"]
|
|
224
|
+
|
|
225
|
+
[build]
|
|
226
|
+
docker-default-platform = "linux/amd64"
|
|
227
|
+
`;
|
|
228
|
+
fs.writeFileSync(path.join(projectPath, 'Cross.toml'), crossToml, 'utf8');
|
|
229
|
+
// cross/Dockerfile
|
|
230
|
+
const crossDir = path.join(projectPath, 'cross');
|
|
231
|
+
fs.mkdirSync(crossDir, { recursive: true });
|
|
232
|
+
const dockerfile = `FROM --platform=linux/amd64 ubuntu:22.04
|
|
233
|
+
|
|
234
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
235
|
+
|
|
236
|
+
RUN apt-get update && apt-get install -y \\
|
|
237
|
+
build-essential \\
|
|
238
|
+
gcc-aarch64-linux-gnu \\
|
|
239
|
+
libc6-dev-arm64-cross \\
|
|
240
|
+
curl \\
|
|
241
|
+
git \\
|
|
242
|
+
pkg-config \\
|
|
243
|
+
cmake \\
|
|
244
|
+
libwayland-dev \\
|
|
245
|
+
libxkbcommon-dev \\
|
|
246
|
+
libfontconfig1-dev \\
|
|
247
|
+
libssl-dev \\
|
|
248
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
249
|
+
|
|
250
|
+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
251
|
+
ENV PATH="/root/.cargo/bin:\${PATH}"
|
|
252
|
+
|
|
253
|
+
RUN rustup target add aarch64-unknown-linux-gnu
|
|
254
|
+
|
|
255
|
+
ENV PKG_CONFIG_ALLOW_CROSS=1
|
|
256
|
+
ENV TARGET_CC=aarch64-linux-gnu-gcc
|
|
257
|
+
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
|
258
|
+
`;
|
|
259
|
+
fs.writeFileSync(path.join(crossDir, 'Dockerfile'), dockerfile, 'utf8');
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
// Web Scaffolding
|
|
263
|
+
// replicate Rust logic
|
|
264
|
+
const indexHtml = `
|
|
77
265
|
<html>
|
|
78
266
|
<head>
|
|
79
267
|
<title>Sample ${name}</title>
|
|
@@ -83,17 +271,28 @@ async function createCommand({ name, template, locationFolder, quiet, }) {
|
|
|
83
271
|
</body>
|
|
84
272
|
</html>
|
|
85
273
|
`.trim();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
274
|
+
fs.writeFileSync(path.join(projectPath, 'index.html'), indexHtml, 'utf8');
|
|
275
|
+
// assets
|
|
276
|
+
const assetsDir = path.join(projectPath, 'assets');
|
|
277
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
278
|
+
fs.writeFileSync(path.join(assetsDir, 'main.js'), '// sample main.js\n', 'utf8');
|
|
279
|
+
fs.writeFileSync(path.join(assetsDir, 'style.css'), '/* sample style.css */\n', 'utf8');
|
|
280
|
+
}
|
|
92
281
|
}
|
|
93
282
|
// metadata
|
|
94
283
|
const pkg = (0, types_1.createPackage)(name, identifier);
|
|
284
|
+
pkg.displayName = answers.displayName;
|
|
285
|
+
pkg.description = answers.description;
|
|
286
|
+
pkg.author = answers.author;
|
|
287
|
+
pkg.version = answers.version;
|
|
288
|
+
pkg.type = answers.type;
|
|
289
|
+
pkg.maintainer = answers.author; // Default maintainer to author
|
|
95
290
|
const metaStr = JSON.stringify(pkg, null, 2);
|
|
96
291
|
fs.writeFileSync(path.join(projectPath, 'metadata.json'), metaStr, 'utf8');
|
|
292
|
+
// Create a default placeholder logo
|
|
293
|
+
const placeholderLogoBase64 = 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAuSURBVHgB7cExAQAAAMKg9U9tCj8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALwCReAAAd7O664AAAAASUVORK5CYII=';
|
|
294
|
+
const logoBuffer = Buffer.from(placeholderLogoBase64, 'base64');
|
|
295
|
+
fs.writeFileSync(path.join(projectPath, 'dapp-logo.png'), logoBuffer);
|
|
97
296
|
(0, LogService_1.log)(`Project '${name}' created successfully.`).green();
|
|
98
297
|
if (!quiet) {
|
|
99
298
|
(0, LogService_1.log)(`\nNext steps:`).white();
|
|
@@ -107,6 +306,10 @@ async function createCommand({ name, template, locationFolder, quiet, }) {
|
|
|
107
306
|
(0, LogService_1.log)(' npm run build').white();
|
|
108
307
|
(0, LogService_1.log)(' airmoney-cli serve -f dist').white();
|
|
109
308
|
}
|
|
309
|
+
else if (answers.type === 'Native') {
|
|
310
|
+
(0, LogService_1.log)(' cargo run').white();
|
|
311
|
+
(0, LogService_1.log)(' # To build for device, ensure you have the correct target installed.').white();
|
|
312
|
+
}
|
|
110
313
|
else {
|
|
111
314
|
(0, LogService_1.log)(' airmoney-cli serve').white();
|
|
112
315
|
}
|
package/dist/cli/upload.js
CHANGED
|
@@ -38,15 +38,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.uploadCommand = uploadCommand;
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
41
42
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
42
|
-
const md5_1 = __importDefault(require("md5"));
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
|
-
const inquirer_1 = __importDefault(require("inquirer"));
|
|
45
44
|
const metadata_1 = require("../util/metadata");
|
|
46
45
|
const tarball_1 = require("../util/tarball");
|
|
47
46
|
const network_1 = require("../util/network");
|
|
48
47
|
const env_1 = require("../util/env");
|
|
49
48
|
const LogService_1 = require("../service/log/LogService");
|
|
49
|
+
const DappService_1 = require("../service/dapp/DappService");
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
50
52
|
const format_1 = require("../util/format");
|
|
51
53
|
/**
|
|
52
54
|
* Gets the project path based on location folder
|
|
@@ -100,23 +102,63 @@ function formatFileSize(bytes) {
|
|
|
100
102
|
}
|
|
101
103
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
102
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Calculates the MD5 hash of a file using streams to avoid OOM
|
|
107
|
+
*/
|
|
108
|
+
async function calculateFileHash(filePath) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const hash = crypto.createHash('md5');
|
|
111
|
+
const input = fs.createReadStream(filePath);
|
|
112
|
+
input.on('error', reject);
|
|
113
|
+
input.on('data', chunk => hash.update(chunk));
|
|
114
|
+
input.on('end', () => {
|
|
115
|
+
resolve(hash.digest('hex'));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
103
119
|
/**
|
|
104
120
|
* Prepares the package by packing and reading it
|
|
105
121
|
*/
|
|
106
122
|
async function preparePackage(meta, projectPath) {
|
|
107
123
|
const pkgName = (0, metadata_1.getPackageName)(meta);
|
|
108
|
-
(
|
|
109
|
-
|
|
124
|
+
if (meta.type === 'Native') {
|
|
125
|
+
(0, LogService_1.log)(`Preparing Native package for ${meta.displayName}...`).white();
|
|
126
|
+
// 1. Find the binary
|
|
127
|
+
const binName = "user_app";
|
|
128
|
+
const binPath = path_1.default.join(projectPath, 'target', 'aarch64-unknown-linux-gnu', 'release', binName);
|
|
129
|
+
if (!fs.existsSync(binPath)) {
|
|
130
|
+
throw new Error('Native binary not found. Please run "airmoney-cli build" first to compile your application.');
|
|
131
|
+
}
|
|
132
|
+
// 2. Prepare packaging in a temp directory to avoid including target/ and other junk
|
|
133
|
+
const tempDir = fs.mkdtempSync(path_1.default.join(os.tmpdir(), 'airmoney-upload-'));
|
|
134
|
+
try {
|
|
135
|
+
fs.copyFileSync(path_1.default.join(projectPath, 'metadata.json'), path_1.default.join(tempDir, 'metadata.json'));
|
|
136
|
+
const logoPath = path_1.default.join(projectPath, 'dapp-logo.png');
|
|
137
|
+
if (fs.existsSync(logoPath)) {
|
|
138
|
+
fs.copyFileSync(logoPath, path_1.default.join(tempDir, 'dapp-logo.png'));
|
|
139
|
+
}
|
|
140
|
+
fs.copyFileSync(binPath, path_1.default.join(tempDir, "user_app"));
|
|
141
|
+
(0, LogService_1.log)(`Packing ${pkgName}...`).white();
|
|
142
|
+
await (0, tarball_1.packProject)(meta, tempDir);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
(0, LogService_1.log)(`Packing Web package ${pkgName}...`).white();
|
|
150
|
+
await (0, tarball_1.packProject)(meta, projectPath);
|
|
151
|
+
}
|
|
110
152
|
const pkgPath = path_1.default.join(process.cwd(), pkgName);
|
|
111
|
-
|
|
153
|
+
const stats = fs.statSync(pkgPath);
|
|
154
|
+
if (!stats.isFile()) {
|
|
112
155
|
throw new Error(`Package file not found at ${pkgPath}`);
|
|
113
156
|
}
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const fileSize = fileBuffer.length;
|
|
157
|
+
const fileHash = await calculateFileHash(pkgPath);
|
|
158
|
+
const fileSize = stats.size;
|
|
117
159
|
(0, LogService_1.log)(`Package Hash: ${fileHash}`).white();
|
|
118
160
|
(0, LogService_1.log)(`Package Size: ${formatFileSize(fileSize)}`).white();
|
|
119
|
-
return {
|
|
161
|
+
return { fileHash, pkgPath, fileSize };
|
|
120
162
|
}
|
|
121
163
|
/**
|
|
122
164
|
* Creates the JSON-RPC request body for upload
|
|
@@ -129,20 +171,25 @@ async function preparePackage(meta, projectPath) {
|
|
|
129
171
|
/**
|
|
130
172
|
* Uploads the package to the server
|
|
131
173
|
*/
|
|
132
|
-
async function uploadPackageToServer(network, userId, apiKey, meta,
|
|
174
|
+
async function uploadPackageToServer(network, userId, apiKey, meta, pkgPath, fileSize) {
|
|
133
175
|
const origin = (0, network_1.networkToRpcUrl)(network).replace(/\/+$/, '');
|
|
134
176
|
const metaParam = encodeURIComponent(JSON.stringify(meta));
|
|
135
177
|
const url = `${origin}/upload?user=${userId}&apiKey=${apiKey}&meta=${metaParam}`;
|
|
136
178
|
const res = await (0, node_fetch_1.default)(url, {
|
|
137
179
|
method: 'POST',
|
|
138
|
-
//
|
|
139
|
-
body:
|
|
180
|
+
// Stream the file directly from disk
|
|
181
|
+
body: fs.createReadStream(pkgPath),
|
|
140
182
|
headers: {
|
|
141
183
|
'Content-Type': 'application/octet-stream',
|
|
184
|
+
'Content-Length': fileSize.toString(),
|
|
142
185
|
},
|
|
143
186
|
});
|
|
144
187
|
const text = await res.text();
|
|
145
188
|
if (!res.ok) {
|
|
189
|
+
if (res.status === 403 && text.includes('package is belong to someone else')) {
|
|
190
|
+
(0, LogService_1.log)(`Error: The package '${meta.name}' is already owned by another user.`).red();
|
|
191
|
+
throw new Error(`Upload failed: Package name '${meta.name}' is already taken.`);
|
|
192
|
+
}
|
|
146
193
|
(0, LogService_1.log)(`Error uploading package: ${text}`).red();
|
|
147
194
|
throw new Error(`Upload failed: ${text}`);
|
|
148
195
|
}
|
|
@@ -184,9 +231,24 @@ async function uploadCommand({ network, locationFolder, }) {
|
|
|
184
231
|
(0, LogService_1.log)(`Upload folder: ${path_1.default.resolve(projectPath)}`).white();
|
|
185
232
|
}
|
|
186
233
|
const meta = await loadAndValidateMetadata(locationFolder);
|
|
234
|
+
// Version Conflict Check
|
|
235
|
+
const dappService = new DappService_1.DappService(userId, apiKey, network);
|
|
236
|
+
(0, LogService_1.log)(`Checking for existing versions of ${meta.name}...`).white();
|
|
237
|
+
const existingBuilds = await dappService.fetchDappVersions(meta.name);
|
|
238
|
+
const conflict = existingBuilds.find(b => b.version === meta.version);
|
|
239
|
+
if (conflict) {
|
|
240
|
+
(0, LogService_1.log)(`\nERROR: Version ${meta.version} already exists on the server.`).red();
|
|
241
|
+
(0, LogService_1.log)(`Status: ${conflict.status || 'Unknown'}`).red();
|
|
242
|
+
(0, LogService_1.log)(`Please bump your version in metadata.json before uploading.`).yellow();
|
|
243
|
+
throw new Error(`Version conflict: ${meta.version} already exists.`);
|
|
244
|
+
}
|
|
245
|
+
// Populate build metadata before upload/packaging
|
|
246
|
+
(0, LogService_1.log)('Populating build metadata...').white();
|
|
247
|
+
(0, metadata_1.populateBuildMetadata)(meta, projectPath);
|
|
248
|
+
(0, metadata_1.saveMetadata)(meta, projectPath);
|
|
187
249
|
packageData = await preparePackage(meta, projectPath);
|
|
188
250
|
(0, LogService_1.log)('Publishing package to DEGN Dapp Store...').white();
|
|
189
|
-
await uploadPackageToServer(network || "devnet", userId, apiKey, meta, packageData.
|
|
251
|
+
await uploadPackageToServer(network || "devnet", userId, apiKey, meta, packageData.pkgPath, packageData.fileSize);
|
|
190
252
|
}
|
|
191
253
|
catch (err) {
|
|
192
254
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
package/dist/config.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const commander_1 = require("commander");
|
|
|
5
5
|
const demo_1 = require("./cli/demo");
|
|
6
6
|
const env_1 = require("./util/env");
|
|
7
7
|
const create_1 = require("./cli/create");
|
|
8
|
+
const build_1 = require("./cli/build");
|
|
8
9
|
const serve_1 = require("./cli/serve");
|
|
9
10
|
const upload_1 = require("./cli/upload");
|
|
10
11
|
const setup_1 = require("./cli/setup");
|
|
@@ -49,6 +50,19 @@ program
|
|
|
49
50
|
process.exit(1);
|
|
50
51
|
}
|
|
51
52
|
});
|
|
53
|
+
program
|
|
54
|
+
.command('build')
|
|
55
|
+
.description('Build and package the project')
|
|
56
|
+
.option('-f, --app-path <string>', 'path to the project')
|
|
57
|
+
.action(async (opts) => {
|
|
58
|
+
try {
|
|
59
|
+
const { appPath } = opts;
|
|
60
|
+
await (0, build_1.buildCommand)({ locationFolder: appPath });
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
52
66
|
program
|
|
53
67
|
.command('serve')
|
|
54
68
|
.description('Serve locally in the simulator')
|
|
@@ -87,6 +87,12 @@ class DappService {
|
|
|
87
87
|
async validateApiKey() {
|
|
88
88
|
return await this.makeJsonRpcRequest('checkApiKey', [this.userId, this.apiKey]);
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Checks if a dapp name (identifier) is available
|
|
92
|
+
*/
|
|
93
|
+
async checkDappName(name) {
|
|
94
|
+
return await this.makeJsonRpcRequest('checkDappName', [this.userId, this.apiKey, name]);
|
|
95
|
+
}
|
|
90
96
|
/**
|
|
91
97
|
* Fetches dapp list from API
|
|
92
98
|
*/
|
|
@@ -43,17 +43,17 @@ class LogBuilder {
|
|
|
43
43
|
this.execute();
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Sets the color to
|
|
46
|
+
* Sets the color to yellow and logs
|
|
47
47
|
*/
|
|
48
|
-
|
|
49
|
-
this.color = '
|
|
48
|
+
yellow() {
|
|
49
|
+
this.color = 'yellow';
|
|
50
50
|
this.execute();
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Sets the color to
|
|
53
|
+
* Sets the color to white and logs
|
|
54
54
|
*/
|
|
55
|
-
|
|
56
|
-
this.color = '
|
|
55
|
+
white() {
|
|
56
|
+
this.color = 'white';
|
|
57
57
|
this.execute();
|
|
58
58
|
}
|
|
59
59
|
/**
|
package/dist/types.js
CHANGED
package/dist/util/metadata.js
CHANGED
|
@@ -36,9 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.loadMetadata = loadMetadata;
|
|
37
37
|
exports.saveMetadata = saveMetadata;
|
|
38
38
|
exports.getPackageName = getPackageName;
|
|
39
|
+
exports.populateBuildMetadata = populateBuildMetadata;
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
40
41
|
const path = __importStar(require("path"));
|
|
41
42
|
const LogService_1 = require("../service/log/LogService");
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
42
44
|
const remote_1 = require("./remote");
|
|
43
45
|
async function loadMetadata(projectPath = '.', appUrl) {
|
|
44
46
|
try {
|
|
@@ -82,6 +84,34 @@ function saveMetadata(pkg, projectPath = '.') {
|
|
|
82
84
|
fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
83
85
|
}
|
|
84
86
|
function getPackageName(pkg) {
|
|
85
|
-
// e.g.
|
|
86
|
-
return `${pkg.
|
|
87
|
+
// e.g. myApp-0.1.0.zip
|
|
88
|
+
return `${pkg.name}-${pkg.version}.zip`;
|
|
89
|
+
}
|
|
90
|
+
function populateBuildMetadata(pkg, projectPath) {
|
|
91
|
+
// 1. Build Number: Increment if numeric, or use timestamp
|
|
92
|
+
const currentBuild = parseInt(pkg.buildNumber || '0', 10);
|
|
93
|
+
if (!isNaN(currentBuild)) {
|
|
94
|
+
pkg.buildNumber = (currentBuild + 1).toString();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
pkg.buildNumber = Math.floor(Date.now() / 1000).toString();
|
|
98
|
+
}
|
|
99
|
+
// 2. Commit Hash
|
|
100
|
+
try {
|
|
101
|
+
const hash = (0, child_process_1.execSync)('git rev-parse HEAD', { cwd: projectPath, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
102
|
+
.toString()
|
|
103
|
+
.trim();
|
|
104
|
+
pkg.commitHash = hash;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
pkg.commitHash = 'n/a';
|
|
108
|
+
}
|
|
109
|
+
// 3. Build Date: YYYYMMDD-HHmm (local)
|
|
110
|
+
const now = new Date();
|
|
111
|
+
const year = now.getFullYear();
|
|
112
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
113
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
114
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
115
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
116
|
+
pkg.buildDate = `${year}${month}${day}-${hours}${minutes}`;
|
|
87
117
|
}
|
package/dist/util/tarball.js
CHANGED
|
@@ -39,11 +39,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.packProject = packProject;
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
-
const
|
|
43
|
-
const
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
44
44
|
const metadata_1 = require("./metadata");
|
|
45
45
|
const LogService_1 = require("../service/log/LogService");
|
|
46
|
-
const
|
|
46
|
+
const REQUIRED_FILES_WEB = ['metadata.json', 'dapp-logo.png', 'index.html'];
|
|
47
|
+
const REQUIRED_FILES_NATIVE = ['metadata.json', 'dapp-logo.png'];
|
|
47
48
|
/**
|
|
48
49
|
* Validates that the project path exists
|
|
49
50
|
*/
|
|
@@ -55,9 +56,10 @@ function validateProjectPath(projectPath) {
|
|
|
55
56
|
/**
|
|
56
57
|
* Validates that all required files exist at the root of the project
|
|
57
58
|
*/
|
|
58
|
-
function validateRequiredFiles(projectPath) {
|
|
59
|
+
function validateRequiredFiles(projectPath, pkg) {
|
|
59
60
|
const missingFiles = [];
|
|
60
|
-
|
|
61
|
+
const required = pkg.type === 'Native' ? REQUIRED_FILES_NATIVE : REQUIRED_FILES_WEB;
|
|
62
|
+
required.forEach(file => {
|
|
61
63
|
const filePath = path.join(projectPath, file);
|
|
62
64
|
if (!fs.existsSync(filePath)) {
|
|
63
65
|
missingFiles.push(file);
|
|
@@ -68,78 +70,77 @@ function validateRequiredFiles(projectPath) {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
/**
|
|
71
|
-
*
|
|
73
|
+
* Creates the zip file
|
|
72
74
|
*/
|
|
73
|
-
function
|
|
74
|
-
const items = fs.readdirSync(dir);
|
|
75
|
-
items.forEach(item => {
|
|
76
|
-
const fullPath = path.join(dir, item);
|
|
77
|
-
const stat = fs.statSync(fullPath);
|
|
78
|
-
if (stat.isDirectory()) {
|
|
79
|
-
getAllFiles(fullPath, baseDir, fileList);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
const relativePath = path.relative(baseDir, fullPath);
|
|
83
|
-
fileList.push(relativePath);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
return fileList;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Collects all files from the project directory
|
|
90
|
-
*/
|
|
91
|
-
function collectProjectFiles(projectPath) {
|
|
92
|
-
const allFiles = getAllFiles(projectPath, projectPath);
|
|
93
|
-
if (allFiles.length === 0) {
|
|
94
|
-
throw new Error(`No files found in ${projectPath}`);
|
|
95
|
-
}
|
|
96
|
-
return allFiles;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Creates the tarball file
|
|
100
|
-
*/
|
|
101
|
-
async function createTarball(outputPath, projectPath, files) {
|
|
75
|
+
async function createZip(outputPath, projectPath) {
|
|
102
76
|
// Clean up any existing output file
|
|
103
77
|
if (fs.existsSync(outputPath)) {
|
|
104
78
|
fs.rmSync(outputPath);
|
|
105
79
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const output = fs.createWriteStream(outputPath);
|
|
82
|
+
const archive = (0, archiver_1.default)('zip', {
|
|
83
|
+
zlib: { level: 9 } // Sets the compression level.
|
|
84
|
+
});
|
|
85
|
+
output.on('close', function () {
|
|
86
|
+
resolve();
|
|
87
|
+
});
|
|
88
|
+
archive.on('warning', function (err) {
|
|
89
|
+
if (err.code === 'ENOENT') {
|
|
90
|
+
// log warning
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
reject(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
archive.on('error', function (err) {
|
|
97
|
+
reject(err);
|
|
98
|
+
});
|
|
99
|
+
archive.pipe(output);
|
|
100
|
+
// append files from a directory, putting its contents at the root of archive
|
|
101
|
+
// ignore common junk files
|
|
102
|
+
archive.directory(projectPath, false, (data) => {
|
|
103
|
+
const ignoredFolders = ['node_modules', '.git', 'target', '.DS_Store'];
|
|
104
|
+
if (ignoredFolders.some(folder => data.name.startsWith(folder))) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return data;
|
|
108
|
+
});
|
|
109
|
+
archive.finalize();
|
|
110
|
+
});
|
|
116
111
|
}
|
|
117
112
|
/**
|
|
118
|
-
* Calculates and logs the MD5 hash of the
|
|
113
|
+
* Calculates and logs the MD5 hash of the zip using streams to avoid OOM
|
|
119
114
|
*/
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
async function logPackageHash(outputPath) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const hash = crypto.createHash('md5');
|
|
118
|
+
const input = fs.createReadStream(outputPath);
|
|
119
|
+
input.on('error', reject);
|
|
120
|
+
input.on('data', chunk => hash.update(chunk));
|
|
121
|
+
input.on('end', () => {
|
|
122
|
+
const digest = hash.digest('hex');
|
|
123
|
+
(0, LogService_1.log)(`MD5: ${digest}`).white();
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
124
127
|
}
|
|
125
128
|
/**
|
|
126
|
-
* Main function to pack a project into a
|
|
129
|
+
* Main function to pack a project into a zip file
|
|
127
130
|
*/
|
|
128
131
|
async function packProject(pkg, projectPath) {
|
|
129
132
|
const outputFilename = (0, metadata_1.getPackageName)(pkg);
|
|
130
133
|
const absOutputPath = path.join(process.cwd(), outputFilename);
|
|
131
134
|
try {
|
|
132
135
|
validateProjectPath(projectPath);
|
|
133
|
-
validateRequiredFiles(projectPath);
|
|
134
|
-
|
|
135
|
-
(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
logTarballHash(absOutputPath);
|
|
139
|
-
(0, LogService_1.log)(`Tarball created at ${absOutputPath}`).white();
|
|
136
|
+
validateRequiredFiles(projectPath, pkg);
|
|
137
|
+
(0, LogService_1.log)('Creating package...').white();
|
|
138
|
+
await createZip(absOutputPath, projectPath);
|
|
139
|
+
await logPackageHash(absOutputPath);
|
|
140
|
+
(0, LogService_1.log)(`Package created at ${absOutputPath}`).white();
|
|
140
141
|
}
|
|
141
142
|
catch (err) {
|
|
142
|
-
(0, LogService_1.log)(`Failed to create
|
|
143
|
+
(0, LogService_1.log)(`Failed to create package: ${err.message}`).red();
|
|
143
144
|
if (fs.existsSync(absOutputPath)) {
|
|
144
145
|
fs.rmSync(absOutputPath);
|
|
145
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airmoney-degn/airmoney-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "airmoney-cli is a command-line interface tool designed to facilitate the development and management of decentralized applications (DApps) for Airmoney.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@solana/signers": "2.1.1",
|
|
33
33
|
"@solana/web3.js": "1.98.2",
|
|
34
|
+
"@types/archiver": "^7.0.0",
|
|
35
|
+
"archiver": "^7.0.1",
|
|
34
36
|
"base64-js": "^1.5.1",
|
|
35
37
|
"bs58": "5.0.0",
|
|
36
38
|
"commander": "^11.0.0",
|
package/readme.md
CHANGED
|
@@ -7,18 +7,40 @@ It simplifies the process of creating, serving, and publishing applications to t
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Setup**: Setup your development environment with ease.
|
|
10
|
-
- **Create**: Initialize new projects with
|
|
10
|
+
- **Create**: Initialize new projects with standard Web or Native (Rust/Slint) templates.
|
|
11
11
|
- **Serve**: Test your project locally for development and testing on Simulator.
|
|
12
|
+
- **Build**: Cross-compile native applications for AirMoney ARM devices.
|
|
12
13
|
- **Upload**: Package and send your project to the DAPP store.
|
|
13
14
|
|
|
14
15
|
## Getting Started
|
|
15
|
-
install `airmoney-cli` using `npm`.
|
|
16
16
|
|
|
17
|
-
### Installation
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
### Installation
|
|
18
|
+
|
|
19
|
+
Install `airmoney-cli` using `npm`:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @airmoney-degn/airmoney-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Native App Development Requirements
|
|
26
|
+
|
|
27
|
+
To build and package **Native Applications**, you must have the following installed:
|
|
28
|
+
1. **Docker**: Used by `cross` for cross-compilation environments.
|
|
29
|
+
2. **Rust & Cargo**: Standard Rust toolchain.
|
|
30
|
+
3. **Cross**: Install via `cargo install cross --git https://github.com/cross-rs/cross`.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
### 1. Setup
|
|
35
|
+
Configure your developer address and API key.
|
|
36
|
+
```bash
|
|
37
|
+
airmoney-cli setup -u <YOUR_WALLET_ADDRESS> -k <YOUR_API_KEY>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Create Project
|
|
41
|
+
Initialize a new project.
|
|
20
42
|
```bash
|
|
21
|
-
|
|
43
|
+
airmoney-cli create -N my-app
|
|
22
44
|
```
|
|
23
45
|
|
|
24
46
|
## Release Process
|