run402 1.57.1 → 1.57.2
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 +2 -0
- package/lib/blob.mjs +29 -4
- package/package.json +1 -1
- package/core-dist/wallet-auth.js +0 -62
- package/core-dist/wallet.js +0 -25
- package/sdk/core-dist/wallet-auth.js +0 -62
- package/sdk/core-dist/wallet.js +0 -25
package/README.md
CHANGED
|
@@ -97,11 +97,13 @@ CI deploys can ship `site`, `functions`, and `database` changes. Keep secrets, d
|
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
run402 blob put ./logo.png # → AssetRef with cdn_url, sri, etag
|
|
100
|
+
run402 blob put ./asset --key assets/logo --content-type image/svg+xml
|
|
100
101
|
run402 blob get <key> --output /tmp/logo.png
|
|
101
102
|
run402 blob diagnose <url> # exit 0 if fresh, 1 if stale
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
The returned `cdn_url` is content-addressed (`pr-<public_id>.run402.com/_blob/<key>-<8hex>.<ext>`) — paste it straight into HTML. SRI is bundled in `sri`.
|
|
106
|
+
`blob put` infers MIME type from the destination key; use `--content-type <mime>` when the key has no useful extension or needs an explicit override.
|
|
105
107
|
|
|
106
108
|
### Functions
|
|
107
109
|
|
package/lib/blob.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* run402 blob — direct-to-S3 storage CLI.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* run402 blob put <file> [files...] [--project <id>] [--key <dest>] [--private] [--immutable] [--concurrency N] [--no-resume]
|
|
5
|
+
* run402 blob put <file> [files...] [--project <id>] [--key <dest>] [--content-type <mime>] [--private] [--immutable] [--concurrency N] [--no-resume]
|
|
6
6
|
* run402 blob get <key> --output <file> [--project <id>]
|
|
7
7
|
* run402 blob ls [--project <id>] [--prefix <p>] [--limit <n>]
|
|
8
8
|
* run402 blob rm <key> [--project <id>]
|
|
@@ -52,6 +52,7 @@ Usage:
|
|
|
52
52
|
Options:
|
|
53
53
|
--project <id> Project ID (defaults to active project from 'run402 projects use')
|
|
54
54
|
--key <dest> Destination key (put only; defaults to file basename)
|
|
55
|
+
--content-type <mime> MIME override for blob put (defaults to extension inference)
|
|
55
56
|
--private Upload as private (not served by CDN; apikey required to read)
|
|
56
57
|
--immutable Adds a content-hash suffix to the URL so overwrites produce distinct URLs.
|
|
57
58
|
Requires computing SHA-256 over the file (CLI does this automatically).
|
|
@@ -84,6 +85,7 @@ Arguments:
|
|
|
84
85
|
Options:
|
|
85
86
|
--project <id> Project ID (defaults to active project from 'run402 projects use')
|
|
86
87
|
--key <dest> Destination key; defaults to file basename. Use trailing '/' as prefix.
|
|
88
|
+
--content-type <mime> MIME override; defaults to inferring from the destination key extension
|
|
87
89
|
--private Upload as private (not served by CDN; apikey required to read)
|
|
88
90
|
--immutable Append content-hash suffix so overwrites produce distinct URLs
|
|
89
91
|
--concurrency N Concurrent part PUTs for multipart uploads (default 4)
|
|
@@ -93,6 +95,7 @@ Options:
|
|
|
93
95
|
Examples:
|
|
94
96
|
run402 blob put ./artifact.tgz --project prj_abc123
|
|
95
97
|
run402 blob put ./dist/**/*.png --project prj_abc123 --key assets/
|
|
98
|
+
run402 blob put ./asset --project prj_abc123 --key assets/logo --content-type image/svg+xml
|
|
96
99
|
run402 blob put huge.bin --project prj_abc123 --immutable --concurrency 8
|
|
97
100
|
`,
|
|
98
101
|
get: `run402 blob get — Download a blob by key
|
|
@@ -202,10 +205,11 @@ function dieApiFailure(prefix, http, body) {
|
|
|
202
205
|
|
|
203
206
|
function parseArgs(rawArgs) {
|
|
204
207
|
const args = normalizeArgv(rawArgs);
|
|
205
|
-
const valueFlags = ["--project", "--key", "--concurrency", "--prefix", "--limit", "--output", "-o", "--ttl"];
|
|
208
|
+
const valueFlags = ["--project", "--key", "--content-type", "--concurrency", "--prefix", "--limit", "--output", "-o", "--ttl"];
|
|
206
209
|
assertKnownFlags(args, [
|
|
207
210
|
"--project",
|
|
208
211
|
"--key",
|
|
212
|
+
"--content-type",
|
|
209
213
|
"--private",
|
|
210
214
|
"--immutable",
|
|
211
215
|
"--concurrency",
|
|
@@ -221,11 +225,12 @@ function parseArgs(rawArgs) {
|
|
|
221
225
|
], valueFlags);
|
|
222
226
|
const out = { positional: [], project: null, key: null, private: false, immutable: false,
|
|
223
227
|
concurrency: 4, resume: true, json: false, prefix: null, limit: null,
|
|
224
|
-
output: null, ttl: null };
|
|
228
|
+
output: null, ttl: null, contentType: null };
|
|
225
229
|
for (let i = 0; i < args.length; i++) {
|
|
226
230
|
const a = args[i];
|
|
227
231
|
if (a === "--project") out.project = args[++i];
|
|
228
232
|
else if (a === "--key") out.key = args[++i];
|
|
233
|
+
else if (a === "--content-type") out.contentType = parseContentTypeFlag("--content-type", args[++i]);
|
|
229
234
|
else if (a === "--private") out.private = true;
|
|
230
235
|
else if (a === "--immutable") out.immutable = true;
|
|
231
236
|
else if (a === "--concurrency") out.concurrency = parseIntegerFlag("--concurrency", args[++i], { min: 1 });
|
|
@@ -240,6 +245,26 @@ function parseArgs(rawArgs) {
|
|
|
240
245
|
return out;
|
|
241
246
|
}
|
|
242
247
|
|
|
248
|
+
function parseContentTypeFlag(name, value) {
|
|
249
|
+
if (value === undefined || value === null) {
|
|
250
|
+
fail({
|
|
251
|
+
code: "BAD_FLAG",
|
|
252
|
+
message: `${name} requires a MIME type value`,
|
|
253
|
+
details: { flag: name },
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const raw = String(value).trim();
|
|
257
|
+
const base = raw.split(";", 1)[0].trim();
|
|
258
|
+
if (!/^[^\s/]+\/[^\s/]+$/.test(base)) {
|
|
259
|
+
fail({
|
|
260
|
+
code: "BAD_FLAG",
|
|
261
|
+
message: `${name} must be a non-empty type/subtype MIME value, got: ${String(value)}`,
|
|
262
|
+
details: { flag: name, value: String(value) },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return raw;
|
|
266
|
+
}
|
|
267
|
+
|
|
243
268
|
async function sha256File(filePath) {
|
|
244
269
|
const h = createHash("sha256");
|
|
245
270
|
const stream = createReadStream(filePath);
|
|
@@ -311,7 +336,7 @@ async function putOne(project, filePath, opts) {
|
|
|
311
336
|
const init = await apiFetch(`${API}/storage/v1/uploads`, "POST", project, {
|
|
312
337
|
key: destKey,
|
|
313
338
|
size_bytes: size,
|
|
314
|
-
content_type: guessContentType(destKey),
|
|
339
|
+
content_type: opts.contentType ?? guessContentType(destKey),
|
|
315
340
|
visibility: opts.private ? "private" : "public",
|
|
316
341
|
immutable: opts.immutable,
|
|
317
342
|
sha256,
|
package/package.json
CHANGED
package/core-dist/wallet-auth.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wallet auth helper — generates EIP-191 signature headers for Run402 API.
|
|
3
|
-
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
-
*/
|
|
5
|
-
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
-
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
7
|
-
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
8
|
-
import { readWallet } from "./wallet.js";
|
|
9
|
-
/**
|
|
10
|
-
* EIP-191 personal_sign: sign a message with the wallet's private key.
|
|
11
|
-
*/
|
|
12
|
-
function personalSign(privateKeyHex, address, message) {
|
|
13
|
-
const msgBytes = new TextEncoder().encode(message);
|
|
14
|
-
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
|
|
15
|
-
const prefixed = new Uint8Array(prefix.length + msgBytes.length);
|
|
16
|
-
prefixed.set(prefix);
|
|
17
|
-
prefixed.set(msgBytes, prefix.length);
|
|
18
|
-
const hash = keccak_256(prefixed);
|
|
19
|
-
const pkHex = privateKeyHex.startsWith("0x")
|
|
20
|
-
? privateKeyHex.slice(2)
|
|
21
|
-
: privateKeyHex;
|
|
22
|
-
const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
|
|
23
|
-
const rawSig = secp256k1.sign(hash, pkBytes);
|
|
24
|
-
const sig = secp256k1.Signature.fromBytes(rawSig);
|
|
25
|
-
// Determine recovery bit by trying both and matching the address
|
|
26
|
-
let recovery = 0;
|
|
27
|
-
for (const v of [0, 1]) {
|
|
28
|
-
try {
|
|
29
|
-
const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
|
|
30
|
-
const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
|
|
31
|
-
const addrBytes = keccak_256(pubBytes).slice(-20);
|
|
32
|
-
if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
|
|
33
|
-
recovery = v;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const r = sig.r.toString(16).padStart(64, "0");
|
|
42
|
-
const s = sig.s.toString(16).padStart(64, "0");
|
|
43
|
-
const vHex = (recovery + 27).toString(16).padStart(2, "0");
|
|
44
|
-
return "0x" + r + s + vHex;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Get wallet auth headers for the Run402 API.
|
|
48
|
-
* Returns null if no wallet is configured.
|
|
49
|
-
*/
|
|
50
|
-
export function getWalletAuthHeaders(walletPath) {
|
|
51
|
-
const wallet = readWallet(walletPath);
|
|
52
|
-
if (!wallet || !wallet.address || !wallet.privateKey)
|
|
53
|
-
return null;
|
|
54
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
55
|
-
const signature = personalSign(wallet.privateKey, wallet.address, `run402:${timestamp}`);
|
|
56
|
-
return {
|
|
57
|
-
"X-Run402-Wallet": wallet.address,
|
|
58
|
-
"X-Run402-Signature": signature,
|
|
59
|
-
"X-Run402-Timestamp": timestamp,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=wallet-auth.js.map
|
package/core-dist/wallet.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, renameSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
4
|
-
import { getWalletPath } from "./config.js";
|
|
5
|
-
export function readWallet(path) {
|
|
6
|
-
const p = path ?? getWalletPath();
|
|
7
|
-
if (!existsSync(p))
|
|
8
|
-
return null;
|
|
9
|
-
try {
|
|
10
|
-
return JSON.parse(readFileSync(p, "utf-8"));
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export function saveWallet(data, path) {
|
|
17
|
-
const p = path ?? getWalletPath();
|
|
18
|
-
const dir = dirname(p);
|
|
19
|
-
mkdirSync(dir, { recursive: true });
|
|
20
|
-
const tmp = join(dir, `.wallet.${randomBytes(4).toString("hex")}.tmp`);
|
|
21
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
22
|
-
renameSync(tmp, p);
|
|
23
|
-
chmodSync(p, 0o600);
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=wallet.js.map
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wallet auth helper — generates EIP-191 signature headers for Run402 API.
|
|
3
|
-
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
-
*/
|
|
5
|
-
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
-
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
7
|
-
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
8
|
-
import { readWallet } from "./wallet.js";
|
|
9
|
-
/**
|
|
10
|
-
* EIP-191 personal_sign: sign a message with the wallet's private key.
|
|
11
|
-
*/
|
|
12
|
-
function personalSign(privateKeyHex, address, message) {
|
|
13
|
-
const msgBytes = new TextEncoder().encode(message);
|
|
14
|
-
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
|
|
15
|
-
const prefixed = new Uint8Array(prefix.length + msgBytes.length);
|
|
16
|
-
prefixed.set(prefix);
|
|
17
|
-
prefixed.set(msgBytes, prefix.length);
|
|
18
|
-
const hash = keccak_256(prefixed);
|
|
19
|
-
const pkHex = privateKeyHex.startsWith("0x")
|
|
20
|
-
? privateKeyHex.slice(2)
|
|
21
|
-
: privateKeyHex;
|
|
22
|
-
const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
|
|
23
|
-
const rawSig = secp256k1.sign(hash, pkBytes);
|
|
24
|
-
const sig = secp256k1.Signature.fromBytes(rawSig);
|
|
25
|
-
// Determine recovery bit by trying both and matching the address
|
|
26
|
-
let recovery = 0;
|
|
27
|
-
for (const v of [0, 1]) {
|
|
28
|
-
try {
|
|
29
|
-
const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
|
|
30
|
-
const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
|
|
31
|
-
const addrBytes = keccak_256(pubBytes).slice(-20);
|
|
32
|
-
if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
|
|
33
|
-
recovery = v;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const r = sig.r.toString(16).padStart(64, "0");
|
|
42
|
-
const s = sig.s.toString(16).padStart(64, "0");
|
|
43
|
-
const vHex = (recovery + 27).toString(16).padStart(2, "0");
|
|
44
|
-
return "0x" + r + s + vHex;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Get wallet auth headers for the Run402 API.
|
|
48
|
-
* Returns null if no wallet is configured.
|
|
49
|
-
*/
|
|
50
|
-
export function getWalletAuthHeaders(walletPath) {
|
|
51
|
-
const wallet = readWallet(walletPath);
|
|
52
|
-
if (!wallet || !wallet.address || !wallet.privateKey)
|
|
53
|
-
return null;
|
|
54
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
55
|
-
const signature = personalSign(wallet.privateKey, wallet.address, `run402:${timestamp}`);
|
|
56
|
-
return {
|
|
57
|
-
"X-Run402-Wallet": wallet.address,
|
|
58
|
-
"X-Run402-Signature": signature,
|
|
59
|
-
"X-Run402-Timestamp": timestamp,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=wallet-auth.js.map
|
package/sdk/core-dist/wallet.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, renameSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
4
|
-
import { getWalletPath } from "./config.js";
|
|
5
|
-
export function readWallet(path) {
|
|
6
|
-
const p = path ?? getWalletPath();
|
|
7
|
-
if (!existsSync(p))
|
|
8
|
-
return null;
|
|
9
|
-
try {
|
|
10
|
-
return JSON.parse(readFileSync(p, "utf-8"));
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export function saveWallet(data, path) {
|
|
17
|
-
const p = path ?? getWalletPath();
|
|
18
|
-
const dir = dirname(p);
|
|
19
|
-
mkdirSync(dir, { recursive: true });
|
|
20
|
-
const tmp = join(dir, `.wallet.${randomBytes(4).toString("hex")}.tmp`);
|
|
21
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
22
|
-
renameSync(tmp, p);
|
|
23
|
-
chmodSync(p, 0o600);
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=wallet.js.map
|