@usearete/a4 0.1.0
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 +47 -0
- package/bin/a4.js +60 -0
- package/package.json +42 -0
- package/scripts/postinstall.js +214 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Arete CLI
|
|
2
|
+
|
|
3
|
+
Programmable data feeds for Solana. Arete CLI lets you deploy feeds for any Solana data to our cloud.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @usearete/a4 create my-app
|
|
9
|
+
cd my-app
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### npm (recommended for JS/TS developers)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @usearete/a4
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Cargo (for Rust developers)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cargo install a4-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
| Command | Description |
|
|
30
|
+
|---------|-------------|
|
|
31
|
+
| `a4 create [name]` | Scaffold a new app from a template |
|
|
32
|
+
| `a4 init` | Initialize a stack project |
|
|
33
|
+
| `a4 up` | Deploy your stack |
|
|
34
|
+
| `a4 status` | Show project overview |
|
|
35
|
+
| `a4 auth login` | Authenticate with Arete |
|
|
36
|
+
|
|
37
|
+
The npm package `@usearete/a4` and the Cargo crate `a4-cli` both install the same `a4` command.
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
- [Getting Started](https://docs.arete.run)
|
|
42
|
+
- [CLI Reference](https://docs.arete.run/cli/commands/)
|
|
43
|
+
- [Stack Development](https://docs.arete.run/building-stacks/workflow/)
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
Apache-2.0
|
package/bin/a4.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
const binName = process.platform === "win32" ? "a4.exe" : "a4";
|
|
8
|
+
const localBinPath = path.join(__dirname, binName);
|
|
9
|
+
|
|
10
|
+
// Try local binary first, then fall back to PATH
|
|
11
|
+
function getBinaryPath() {
|
|
12
|
+
// 1. Check for bundled binary (npm postinstall)
|
|
13
|
+
if (fs.existsSync(localBinPath)) {
|
|
14
|
+
return localBinPath;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 2. Check system PATH (cargo install, manual install)
|
|
18
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
19
|
+
const result = spawnSync(whichCmd, ["a4"], { encoding: "utf8" });
|
|
20
|
+
if (result.status === 0 && result.stdout) {
|
|
21
|
+
const systemBin = result.stdout.trim().split("\n")[0];
|
|
22
|
+
if (fs.existsSync(systemBin)) {
|
|
23
|
+
return systemBin;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const binPath = getBinaryPath();
|
|
31
|
+
|
|
32
|
+
if (!binPath) {
|
|
33
|
+
console.error(
|
|
34
|
+
"Arete CLI binary not found. This usually means the postinstall script failed.\n" +
|
|
35
|
+
"Try reinstalling: npm install @usearete/a4\n" +
|
|
36
|
+
"\n" +
|
|
37
|
+
"If the problem persists, you can install the CLI via Cargo:\n" +
|
|
38
|
+
" cargo install a4-cli"
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = spawnSync(binPath, process.argv.slice(2), {
|
|
44
|
+
stdio: "inherit",
|
|
45
|
+
env: process.env,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (result.error) {
|
|
49
|
+
if (result.error.code === "EACCES") {
|
|
50
|
+
console.error(
|
|
51
|
+
"Permission denied. Try running:\n" +
|
|
52
|
+
` chmod +x "${binPath}"`
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
console.error("Failed to run Arete CLI:", result.error.message);
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
process.exit(result.status ?? 1);
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usearete/a4",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Arete CLI (a4) - Real-time Solana streaming pipelines",
|
|
5
|
+
"bin": {
|
|
6
|
+
"a4": "./bin/a4.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node scripts/postinstall.js"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/AreteA4/arete.git",
|
|
14
|
+
"directory": "packages/arete"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://arete.run",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/AreteA4/arete/issues"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"arete",
|
|
22
|
+
"solana",
|
|
23
|
+
"streaming",
|
|
24
|
+
"cli",
|
|
25
|
+
"blockchain",
|
|
26
|
+
"web3",
|
|
27
|
+
"a4"
|
|
28
|
+
],
|
|
29
|
+
"author": "Arete Team",
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=16.0.0"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"bin",
|
|
39
|
+
"scripts",
|
|
40
|
+
"README.md"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require("https");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
const pkg = require("../package.json");
|
|
9
|
+
const version = pkg.version;
|
|
10
|
+
|
|
11
|
+
const PLATFORMS = {
|
|
12
|
+
"darwin-arm64": "a4-darwin-arm64",
|
|
13
|
+
"darwin-x64": "a4-darwin-x64",
|
|
14
|
+
"linux-x64": "a4-linux-x64",
|
|
15
|
+
"linux-arm64": "a4-linux-arm64",
|
|
16
|
+
"win32-x64": "a4-win32-x64.exe",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const platform = process.platform;
|
|
20
|
+
const arch = process.arch;
|
|
21
|
+
const key = `${platform}-${arch}`;
|
|
22
|
+
|
|
23
|
+
const binaryName = PLATFORMS[key];
|
|
24
|
+
if (!binaryName) {
|
|
25
|
+
console.warn(
|
|
26
|
+
`Arete CLI does not have a prebuilt binary for ${key}.\n` +
|
|
27
|
+
"You can build from source: cargo install a4-cli"
|
|
28
|
+
);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
33
|
+
const binPath = path.join(binDir, platform === "win32" ? "a4.exe" : "a4");
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(binPath)) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const releaseUrl = `https://github.com/AreteA4/arete/releases/download/a4-cli-v${version}`;
|
|
40
|
+
const url = `${releaseUrl}/${binaryName}`;
|
|
41
|
+
const checksumUrl = `${releaseUrl}/checksums.txt`;
|
|
42
|
+
const MAX_REDIRECTS = 5;
|
|
43
|
+
|
|
44
|
+
console.log(`Downloading Arete CLI v${version} for ${key}...`);
|
|
45
|
+
|
|
46
|
+
function removeIfExists(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
fs.unlinkSync(filePath);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (err.code !== "ENOENT") {
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getResponse(url, depth = 0) {
|
|
57
|
+
if (depth > MAX_REDIRECTS) {
|
|
58
|
+
return Promise.reject(new Error("Too many redirects"));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
https
|
|
63
|
+
.get(url, (response) => {
|
|
64
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
65
|
+
if (!response.headers.location) {
|
|
66
|
+
response.resume();
|
|
67
|
+
reject(new Error("Redirect missing location header"));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
response.resume();
|
|
72
|
+
resolve(
|
|
73
|
+
getResponse(
|
|
74
|
+
new URL(response.headers.location, url).toString(),
|
|
75
|
+
depth + 1
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
resolve(response);
|
|
82
|
+
})
|
|
83
|
+
.on("error", reject);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function download(url, dest) {
|
|
88
|
+
const response = await getResponse(url);
|
|
89
|
+
|
|
90
|
+
if (response.statusCode !== 200) {
|
|
91
|
+
response.resume();
|
|
92
|
+
removeIfExists(dest);
|
|
93
|
+
throw new Error(`Download failed: HTTP ${response.statusCode}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const total = parseInt(response.headers["content-length"], 10);
|
|
97
|
+
let downloaded = 0;
|
|
98
|
+
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
const file = fs.createWriteStream(dest, { mode: 0o755 });
|
|
101
|
+
|
|
102
|
+
response.on("data", (chunk) => {
|
|
103
|
+
downloaded += chunk.length;
|
|
104
|
+
if (total && process.stdout.isTTY) {
|
|
105
|
+
const pct = Math.round((downloaded / total) * 100);
|
|
106
|
+
process.stdout.write(`\rDownloading... ${pct}%`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
response.on("error", (err) => {
|
|
111
|
+
file.destroy();
|
|
112
|
+
removeIfExists(dest);
|
|
113
|
+
reject(err);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
file.on("error", (err) => {
|
|
117
|
+
response.destroy();
|
|
118
|
+
removeIfExists(dest);
|
|
119
|
+
reject(err);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
file.on("finish", () => {
|
|
123
|
+
file.close(() => {
|
|
124
|
+
if (process.stdout.isTTY) {
|
|
125
|
+
process.stdout.write("\n");
|
|
126
|
+
}
|
|
127
|
+
resolve();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
response.pipe(file);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function fetchText(url) {
|
|
136
|
+
const response = await getResponse(url);
|
|
137
|
+
|
|
138
|
+
if (response.statusCode !== 200) {
|
|
139
|
+
response.resume();
|
|
140
|
+
throw new Error(`Checksum download failed: HTTP ${response.statusCode}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
let body = "";
|
|
145
|
+
response.setEncoding("utf8");
|
|
146
|
+
response.on("data", (chunk) => {
|
|
147
|
+
body += chunk;
|
|
148
|
+
});
|
|
149
|
+
response.on("end", () => resolve(body));
|
|
150
|
+
response.on("error", reject);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function sha256File(filePath) {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const hash = crypto.createHash("sha256");
|
|
157
|
+
const stream = fs.createReadStream(filePath);
|
|
158
|
+
|
|
159
|
+
stream.on("error", reject);
|
|
160
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
161
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function verifyChecksum(checksumUrl, fileName, filePath) {
|
|
166
|
+
const checksums = await fetchText(checksumUrl);
|
|
167
|
+
const expected = checksums
|
|
168
|
+
.split(/\r?\n/)
|
|
169
|
+
.map((line) => line.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/))
|
|
170
|
+
.find((match) => match && match[2] === fileName);
|
|
171
|
+
|
|
172
|
+
if (!expected) {
|
|
173
|
+
throw new Error(`Missing checksum for ${fileName}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const actual = await sha256File(filePath);
|
|
177
|
+
if (actual !== expected[1].toLowerCase()) {
|
|
178
|
+
throw new Error(`Checksum mismatch for ${fileName}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
try {
|
|
184
|
+
if (!fs.existsSync(binDir)) {
|
|
185
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await download(url, binPath);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await verifyChecksum(checksumUrl, binaryName, binPath);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
removeIfExists(binPath);
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (platform !== "win32") {
|
|
198
|
+
fs.chmodSync(binPath, 0o755);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log("Arete CLI installed successfully.");
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.error(`\nFailed to download Arete CLI: ${err.message}`);
|
|
204
|
+
console.error(
|
|
205
|
+
"\nYou can install manually via Cargo:\n" +
|
|
206
|
+
" cargo install a4-cli\n" +
|
|
207
|
+
"\nOr download directly from:\n" +
|
|
208
|
+
` ${url}`
|
|
209
|
+
);
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
main();
|