@hardkas/cli 0.2.2-alpha → 0.3.0-alpha
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/accounts-keystore-runners-QYSNZMZV.js +155 -0
- package/dist/{artifact-lineage-runner-EPT6ABS2.js → artifact-lineage-runner-TY4YTE6H.js} +6 -2
- package/dist/chunk-K7XPWWIO.js +165 -0
- package/dist/chunk-KA5CAWI2.js +163 -0
- package/dist/chunk-ZM2NBOAE.js +37 -0
- package/dist/{dag-runners-BQAKJ6DM.js → dag-runners-NLBYR7I7.js} +2 -2
- package/dist/deployment-runners-GEICABEH.js +15 -0
- package/dist/index.js +2378 -559
- package/dist/replay-verify-runner-Y3RARVD7.js +50 -0
- package/dist/{rpc-doctor-runner-RKGKFGMM.js → rpc-doctor-runner-V7H7AAX4.js} +1 -1
- package/dist/{snapshot-restore-runner-P26HDE74.js → snapshot-restore-runner-KIJNPLDV.js} +1 -1
- package/dist/{snapshot-verify-runner-UYTXXQ7A.js → snapshot-verify-runner-PGMADWLN.js} +1 -1
- package/dist/{tx-verify-runner-GPPVBQIF.js → tx-verify-runner-6EGY5ZN4.js} +11 -2
- package/dist/ui-SUYOHGGP.js +10 -0
- package/package.json +22 -16
- package/dist/accounts-keystore-runners-CVRE6NVM.js +0 -112
- package/dist/chunk-M54KNJEH.js +0 -98
- package/dist/replay-verify-runner-WBK2FCWC.js +0 -37
- package/dist/ui-DXULTF7Q.js +0 -8
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquirePassword,
|
|
3
|
+
acquirePrivateKey
|
|
4
|
+
} from "./chunk-ZM2NBOAE.js";
|
|
5
|
+
import {
|
|
6
|
+
UI
|
|
7
|
+
} from "./chunk-K7XPWWIO.js";
|
|
8
|
+
|
|
9
|
+
// src/runners/accounts-keystore-runners.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { KeystoreManager, loadOrCreateRealAccountStore, saveRealAccountStore, importRealDevAccount } from "@hardkas/accounts";
|
|
13
|
+
async function runAccountsKeystoreImport(options) {
|
|
14
|
+
const name = options.name || "default";
|
|
15
|
+
const address = options.address;
|
|
16
|
+
if (options.unsafePlaintext) {
|
|
17
|
+
UI.warning("LEGACY MODE: Storing private keys in plaintext is unsafe and discouraged.");
|
|
18
|
+
if (!options.yes) {
|
|
19
|
+
const confirmed = await UI.confirm("Are you sure you want to store this key in plaintext?");
|
|
20
|
+
if (!confirmed) throw new Error("Import cancelled by user.");
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
UI.info("HardKAS: Encrypted keystore is for local developer workflows, not institutional custody.");
|
|
24
|
+
UI.info("Do not import mainnet keys unless you fully understand the risks.");
|
|
25
|
+
}
|
|
26
|
+
if (!address) {
|
|
27
|
+
throw new Error("Address is required for import.");
|
|
28
|
+
}
|
|
29
|
+
let privateKeyUsedAsArg = false;
|
|
30
|
+
let finalKey;
|
|
31
|
+
if (options.privateKeyStdin || options.privateKeyEnv) {
|
|
32
|
+
finalKey = await acquirePrivateKey({
|
|
33
|
+
stdin: !!options.privateKeyStdin,
|
|
34
|
+
env: options.privateKeyEnv,
|
|
35
|
+
message: `Enter private key for account '${name}':`
|
|
36
|
+
});
|
|
37
|
+
} else if (options.privateKey) {
|
|
38
|
+
finalKey = options.privateKey;
|
|
39
|
+
privateKeyUsedAsArg = true;
|
|
40
|
+
} else {
|
|
41
|
+
finalKey = await acquirePrivateKey({
|
|
42
|
+
message: `Enter private key for account '${name}':`
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (privateKeyUsedAsArg) {
|
|
46
|
+
const warningMsg = "--private-key may be recorded in shell history, process lists, CI logs, or terminal scrollback.";
|
|
47
|
+
const suggestion = "Use --private-key-stdin or --private-key-env instead.";
|
|
48
|
+
if (!options.json) {
|
|
49
|
+
UI.securityWarning("PRIVATE_KEY_ARG_DEPRECATED", warningMsg, suggestion);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!finalKey) {
|
|
53
|
+
throw new Error("Private key is required for import.");
|
|
54
|
+
}
|
|
55
|
+
let keystoreRef;
|
|
56
|
+
if (!options.unsafePlaintext) {
|
|
57
|
+
const password = await acquirePassword({
|
|
58
|
+
stdin: !!options.passwordStdin,
|
|
59
|
+
env: options.passwordEnv,
|
|
60
|
+
message: `Enter new keystore password for account '${name}':`
|
|
61
|
+
});
|
|
62
|
+
if (!password) {
|
|
63
|
+
throw new Error("Password cannot be empty for encrypted storage.");
|
|
64
|
+
}
|
|
65
|
+
const keystore = await KeystoreManager.createEncryptedKeystore(
|
|
66
|
+
{
|
|
67
|
+
address,
|
|
68
|
+
privateKey: finalKey,
|
|
69
|
+
network: address.startsWith("kaspa:") ? "mainnet" : "devnet"
|
|
70
|
+
},
|
|
71
|
+
password,
|
|
72
|
+
{
|
|
73
|
+
label: name,
|
|
74
|
+
network: address.startsWith("kaspa:") ? "mainnet" : "devnet"
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
const keystoreDir = path.join(process.cwd(), ".hardkas", "keystore");
|
|
78
|
+
const filePath = path.join(keystoreDir, `${name}.json`);
|
|
79
|
+
await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
|
|
80
|
+
keystoreRef = `.hardkas/keystore/${name}.json`;
|
|
81
|
+
}
|
|
82
|
+
let store = await loadOrCreateRealAccountStore();
|
|
83
|
+
store = importRealDevAccount(store, {
|
|
84
|
+
name,
|
|
85
|
+
address,
|
|
86
|
+
...options.unsafePlaintext ? { privateKey: finalKey } : {},
|
|
87
|
+
...keystoreRef ? { keystoreRef } : {}
|
|
88
|
+
});
|
|
89
|
+
await saveRealAccountStore(store);
|
|
90
|
+
const warnings = [];
|
|
91
|
+
if (privateKeyUsedAsArg) {
|
|
92
|
+
warnings.push({
|
|
93
|
+
code: "PRIVATE_KEY_ARG_DEPRECATED",
|
|
94
|
+
severity: "warning",
|
|
95
|
+
message: "--private-key is deprecated and unsafe. Use --private-key-stdin or --private-key-env."
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
name,
|
|
101
|
+
encrypted: !options.unsafePlaintext,
|
|
102
|
+
warnings,
|
|
103
|
+
formatted: options.unsafePlaintext ? `\u2713 Successfully imported account '${name}' (UNSAFE PLAINTEXT)` : `\u2713 Successfully imported and encrypted account '${name}'`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function runAccountsSessionOpen(options) {
|
|
107
|
+
const { name } = options;
|
|
108
|
+
const filePath = path.join(process.cwd(), ".hardkas", "keystore", `${name}.json`);
|
|
109
|
+
if (!fs.existsSync(filePath)) {
|
|
110
|
+
throw new Error(`Keystore for account '${name}' not found at ${filePath}`);
|
|
111
|
+
}
|
|
112
|
+
const keystore = await KeystoreManager.loadEncryptedKeystore(filePath);
|
|
113
|
+
const password = await acquirePassword({
|
|
114
|
+
stdin: !!options.passwordStdin,
|
|
115
|
+
env: options.passwordEnv,
|
|
116
|
+
message: `Enter password for account '${name}':`
|
|
117
|
+
});
|
|
118
|
+
const result = await KeystoreManager.decryptEncryptedKeystore(keystore, password);
|
|
119
|
+
if (result.success) {
|
|
120
|
+
UI.success(`Access to account '${name}' verified.`);
|
|
121
|
+
UI.info("Note: This does not create a production wallet session or daemon.");
|
|
122
|
+
UI.info("Decrypted key material is process-local and will not be persisted.");
|
|
123
|
+
} else {
|
|
124
|
+
throw new Error(result.error || "Failed to unlock keystore.");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function runAccountsKeystoreChangePassword(options) {
|
|
128
|
+
const { name } = options;
|
|
129
|
+
const filePath = path.join(process.cwd(), ".hardkas", "keystore", `${name}.json`);
|
|
130
|
+
const keystore = await KeystoreManager.loadEncryptedKeystore(filePath);
|
|
131
|
+
const oldPassword = await acquirePassword({
|
|
132
|
+
message: "Enter current password:"
|
|
133
|
+
});
|
|
134
|
+
const newPassword = await acquirePassword({
|
|
135
|
+
message: "Enter new password:"
|
|
136
|
+
});
|
|
137
|
+
const confirm = await acquirePassword({
|
|
138
|
+
message: "Confirm new password:"
|
|
139
|
+
});
|
|
140
|
+
if (newPassword !== confirm) {
|
|
141
|
+
throw new Error("New passwords do not match.");
|
|
142
|
+
}
|
|
143
|
+
const updatedKeystore = await KeystoreManager.changeKeystorePassword(
|
|
144
|
+
keystore,
|
|
145
|
+
oldPassword,
|
|
146
|
+
newPassword
|
|
147
|
+
);
|
|
148
|
+
await KeystoreManager.saveEncryptedKeystore(filePath, updatedKeystore);
|
|
149
|
+
UI.success(`Successfully changed password for account '${name}'.`);
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
runAccountsKeystoreChangePassword,
|
|
153
|
+
runAccountsKeystoreImport,
|
|
154
|
+
runAccountsSessionOpen
|
|
155
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
UI
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-K7XPWWIO.js";
|
|
4
4
|
|
|
5
5
|
// src/runners/artifact-lineage-runner.ts
|
|
6
6
|
import {
|
|
@@ -49,7 +49,11 @@ async function runArtifactLineage(options) {
|
|
|
49
49
|
const result = verifyLineage(artifact);
|
|
50
50
|
if (!result.ok) {
|
|
51
51
|
console.log("\nLineage Violations:");
|
|
52
|
-
result.issues.forEach((i) =>
|
|
52
|
+
result.issues.forEach((i) => {
|
|
53
|
+
const prefix = i.severity === "error" ? "\u2717" : "\u26A0";
|
|
54
|
+
console.log(` ${prefix} [${i.code}] ${i.message}`);
|
|
55
|
+
});
|
|
56
|
+
process.exitCode = 1;
|
|
53
57
|
} else {
|
|
54
58
|
console.log("\n\u2713 Internal lineage structure is consistent.");
|
|
55
59
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/ui.ts
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { formatSompi, maskSecrets } from "@hardkas/core";
|
|
4
|
+
var UI = {
|
|
5
|
+
header(text) {
|
|
6
|
+
const masked = maskSecrets(text);
|
|
7
|
+
console.log(pc.bold(pc.magenta(`
|
|
8
|
+
\u2550\u2550\u2550 ${masked} \u2550\u2550\u2550`)));
|
|
9
|
+
},
|
|
10
|
+
divider() {
|
|
11
|
+
console.log(pc.dim(" " + "\u2500".repeat(50)));
|
|
12
|
+
},
|
|
13
|
+
info(text) {
|
|
14
|
+
console.log(` ${pc.blue("\u2139")} ${text}`);
|
|
15
|
+
},
|
|
16
|
+
success(text) {
|
|
17
|
+
console.log(` ${pc.green("\u2714")} ${text}`);
|
|
18
|
+
},
|
|
19
|
+
box(title, subtitle) {
|
|
20
|
+
const width = 40;
|
|
21
|
+
console.log(pc.magenta(` \u2554${"\u2550".repeat(width - 2)}\u2557`));
|
|
22
|
+
console.log(pc.magenta(` \u2551${pc.bold(pc.white(title.padStart((width - 2 + title.length) / 2).padEnd(width - 2)))}\u2551`));
|
|
23
|
+
if (subtitle) {
|
|
24
|
+
console.log(pc.magenta(` \u2551${pc.italic(pc.dim(subtitle.padStart((width - 2 + subtitle.length) / 2).padEnd(width - 2)))}\u2551`));
|
|
25
|
+
}
|
|
26
|
+
console.log(pc.magenta(` \u255A${"\u2550".repeat(width - 2)}\u255D`));
|
|
27
|
+
console.log("");
|
|
28
|
+
},
|
|
29
|
+
warning(text) {
|
|
30
|
+
const masked = maskSecrets(text);
|
|
31
|
+
console.log(pc.yellow(`
|
|
32
|
+
\u26A0\uFE0F WARNING:`));
|
|
33
|
+
console.log(pc.yellow(` ${masked}`));
|
|
34
|
+
},
|
|
35
|
+
securityWarning(code, message, suggestion) {
|
|
36
|
+
console.log(pc.yellow(`
|
|
37
|
+
\u26A0\uFE0F SECURITY WARNING [${code}]:`));
|
|
38
|
+
console.log(pc.yellow(` ${message}`));
|
|
39
|
+
if (suggestion) {
|
|
40
|
+
console.log(pc.cyan(`
|
|
41
|
+
\u{1F4A1} Suggestion:`));
|
|
42
|
+
console.log(pc.cyan(` ${suggestion}`));
|
|
43
|
+
}
|
|
44
|
+
console.log("");
|
|
45
|
+
},
|
|
46
|
+
error(msg, suggestion) {
|
|
47
|
+
const maskedMsg = maskSecrets(msg);
|
|
48
|
+
const maskedSuggestion = suggestion ? maskSecrets(suggestion) : void 0;
|
|
49
|
+
console.error(pc.red(`
|
|
50
|
+
\u2717 Error:`));
|
|
51
|
+
console.error(pc.red(` ${maskedMsg}`));
|
|
52
|
+
if (maskedSuggestion) {
|
|
53
|
+
console.error(pc.cyan(`
|
|
54
|
+
\u{1F4A1} Suggestion:`));
|
|
55
|
+
console.error(pc.cyan(` ${maskedSuggestion}`));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
field(label, value) {
|
|
59
|
+
const val = value === void 0 || value === null ? pc.dim("none") : maskSecrets(String(value));
|
|
60
|
+
console.log(` ${pc.dim(label.padEnd(16))} ${pc.white(val)}`);
|
|
61
|
+
},
|
|
62
|
+
kas(label, sompi) {
|
|
63
|
+
this.field(label, pc.cyan(formatSompi(BigInt(sompi))));
|
|
64
|
+
},
|
|
65
|
+
maturity(label) {
|
|
66
|
+
const colors = {
|
|
67
|
+
stable: pc.green,
|
|
68
|
+
preview: pc.blue,
|
|
69
|
+
experimental: pc.yellow,
|
|
70
|
+
research: pc.magenta,
|
|
71
|
+
internal: pc.dim
|
|
72
|
+
};
|
|
73
|
+
const color = colors[label.toLowerCase()] || pc.white;
|
|
74
|
+
return color(label.toLowerCase());
|
|
75
|
+
},
|
|
76
|
+
async confirm(message) {
|
|
77
|
+
const readline = await import("readline/promises");
|
|
78
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
79
|
+
const answer = await rl.question(pc.yellow(` \u26A0\uFE0F ${message} (y/N): `));
|
|
80
|
+
rl.close();
|
|
81
|
+
return answer.toLowerCase() === "y";
|
|
82
|
+
},
|
|
83
|
+
footer(hint) {
|
|
84
|
+
if (hint) {
|
|
85
|
+
console.log(pc.dim(`
|
|
86
|
+
Hint: ${hint}`));
|
|
87
|
+
}
|
|
88
|
+
console.log("");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function handleError(e, context) {
|
|
92
|
+
const rawMsg = e instanceof Error ? e.message : String(e);
|
|
93
|
+
const msg = maskSecrets ? maskSecrets(rawMsg) : rawMsg;
|
|
94
|
+
const errorObj = e;
|
|
95
|
+
let reason = maskSecrets ? maskSecrets(errorObj.reason) : errorObj.reason;
|
|
96
|
+
let suggestion = maskSecrets ? maskSecrets(errorObj.suggestion) : errorObj.suggestion;
|
|
97
|
+
if (msg === "Real transaction signing is not available") {
|
|
98
|
+
console.error(`
|
|
99
|
+
${msg}`);
|
|
100
|
+
if (reason) console.error(`
|
|
101
|
+
Reason:
|
|
102
|
+
${reason}`);
|
|
103
|
+
if (suggestion) console.error(`
|
|
104
|
+
Suggestion:
|
|
105
|
+
${suggestion}
|
|
106
|
+
No artifact was written.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!suggestion) {
|
|
110
|
+
if (msg.includes("Localnet state not found")) {
|
|
111
|
+
suggestion = "Run 'hardkas localnet reset' to initialize the simulated environment.";
|
|
112
|
+
} else if (msg.includes("Insufficient funds")) {
|
|
113
|
+
suggestion = "Use 'hardkas faucet <address> <amount>' to add funds to your account.";
|
|
114
|
+
} else if (msg.includes("Account not found")) {
|
|
115
|
+
suggestion = "Check your 'hardkas.config.ts' or use a full Kaspa address.";
|
|
116
|
+
} else if (msg.includes("Docker") || msg.includes("container")) {
|
|
117
|
+
suggestion = "Ensure Docker is running and you have permissions to manage containers.";
|
|
118
|
+
} else if (msg.includes("L2 RPC") || msg.includes("L2 profile")) {
|
|
119
|
+
suggestion = "Check your L2 network configuration or pass a valid --url.";
|
|
120
|
+
} else if (msg.includes("RPC") || msg.includes("Connection refused")) {
|
|
121
|
+
suggestion = "The Kaspa node might still be starting. Try 'hardkas rpc health --wait'.";
|
|
122
|
+
} else if (msg.includes("submitTransaction is not exposed")) {
|
|
123
|
+
suggestion = "Ensure your node/RPC provider supports transaction submission and you are NOT on mainnet without --allow-mainnet-signing.";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
UI.error(context ? `${context}: ${msg}` : msg, suggestion);
|
|
127
|
+
}
|
|
128
|
+
function handleLockError(e) {
|
|
129
|
+
const code = e.code || "UNKNOWN_ERROR";
|
|
130
|
+
const meta = e.cause;
|
|
131
|
+
if (code === "LOCK_HELD" || code === "LOCK_TIMEOUT" || code === "STALE_LOCK") {
|
|
132
|
+
const title = code === "STALE_LOCK" ? "Stale Workspace Lock Detected" : "Workspace is locked by another HardKAS process";
|
|
133
|
+
console.error(pc.red(`
|
|
134
|
+
\u2717 ${pc.bold(title)}`));
|
|
135
|
+
console.error(pc.red(` ${"\u2500".repeat(title.length + 4)}`));
|
|
136
|
+
if (meta) {
|
|
137
|
+
console.error(` ${pc.dim("Lock:")} ${pc.white(meta.name)}`);
|
|
138
|
+
console.error(` ${pc.dim("PID:")} ${pc.white(meta.pid)}`);
|
|
139
|
+
console.error(` ${pc.dim("Command:")} ${pc.white(meta.command)}`);
|
|
140
|
+
console.error(` ${pc.dim("Created:")} ${pc.white(meta.createdAt)}`);
|
|
141
|
+
if (meta.path) console.error(` ${pc.dim("Path:")} ${pc.white(meta.path)}`);
|
|
142
|
+
}
|
|
143
|
+
console.error(pc.cyan(`
|
|
144
|
+
\u{1F4A1} Suggestion:`));
|
|
145
|
+
if (code === "STALE_LOCK") {
|
|
146
|
+
console.error(` The process (PID ${meta?.pid}) appears to be dead.`);
|
|
147
|
+
console.error(` Run 'hardkas lock clear ${meta?.name} --if-dead' to release it safely.`);
|
|
148
|
+
} else {
|
|
149
|
+
console.error(` Wait for the process to finish, or run:`);
|
|
150
|
+
console.error(` hardkas lock doctor`);
|
|
151
|
+
console.error(`
|
|
152
|
+
If you believe the process is dead:`);
|
|
153
|
+
console.error(` hardkas lock clear ${meta?.name} --if-dead`);
|
|
154
|
+
}
|
|
155
|
+
console.error("");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
handleError(e);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
UI,
|
|
163
|
+
handleError,
|
|
164
|
+
handleLockError
|
|
165
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UI
|
|
3
|
+
} from "./chunk-K7XPWWIO.js";
|
|
4
|
+
|
|
5
|
+
// src/runners/deployment-runners.ts
|
|
6
|
+
import { withLock } from "@hardkas/core";
|
|
7
|
+
import {
|
|
8
|
+
saveDeployment,
|
|
9
|
+
loadDeployment,
|
|
10
|
+
listDeployments,
|
|
11
|
+
createDeploymentRecord,
|
|
12
|
+
updateDeploymentStatus
|
|
13
|
+
} from "@hardkas/artifacts";
|
|
14
|
+
import { loadHardkasConfig, resolveNetworkTarget } from "@hardkas/config";
|
|
15
|
+
import { JsonWrpcKaspaClient } from "@hardkas/kaspa-rpc";
|
|
16
|
+
async function trackDeployment(opts) {
|
|
17
|
+
const rootDir = process.cwd();
|
|
18
|
+
await withLock({ rootDir, name: "artifacts", command: "hardkas deploy track" }, async () => {
|
|
19
|
+
const existing = await loadDeployment(rootDir, opts.network, opts.label);
|
|
20
|
+
if (existing) {
|
|
21
|
+
throw new Error(`Deployment '${opts.label}' already exists on network '${opts.network}'.`);
|
|
22
|
+
}
|
|
23
|
+
const record = createDeploymentRecord({
|
|
24
|
+
label: opts.label,
|
|
25
|
+
networkId: opts.network,
|
|
26
|
+
...opts.txId ? { txId: opts.txId } : {},
|
|
27
|
+
...opts.plan ? { planArtifactId: opts.plan } : {},
|
|
28
|
+
...opts.receipt ? { receiptArtifactId: opts.receipt } : {},
|
|
29
|
+
status: opts.status || "sent",
|
|
30
|
+
...opts.notes ? { notes: opts.notes } : {}
|
|
31
|
+
});
|
|
32
|
+
await saveDeployment(rootDir, record);
|
|
33
|
+
UI.success(`Tracked deployment: ${opts.label} (${opts.network})`);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function listAllDeployments(opts) {
|
|
37
|
+
const rootDir = process.cwd();
|
|
38
|
+
const deployments = await listDeployments(rootDir, opts.network);
|
|
39
|
+
const filtered = opts.status ? deployments.filter((d) => d.status === opts.status) : deployments;
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (filtered.length === 0) {
|
|
45
|
+
UI.info("No deployments found.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
UI.header("Deployments");
|
|
49
|
+
const byNetwork = {};
|
|
50
|
+
for (const d of filtered) {
|
|
51
|
+
const net = d.networkId || "unknown";
|
|
52
|
+
if (!byNetwork[net]) byNetwork[net] = [];
|
|
53
|
+
byNetwork[net].push(d);
|
|
54
|
+
}
|
|
55
|
+
let total = 0;
|
|
56
|
+
for (const [net, items] of Object.entries(byNetwork)) {
|
|
57
|
+
console.log(`
|
|
58
|
+
${net}`);
|
|
59
|
+
for (const d of items) {
|
|
60
|
+
const statusIcon = d.status === "confirmed" ? "\u2705" : d.status === "failed" ? "\u274C" : "\u23F3";
|
|
61
|
+
const txIdShort = d.txId ? d.txId.slice(0, 12) + "..." : "(none)";
|
|
62
|
+
const ago = formatAgo(d.deployedAt);
|
|
63
|
+
console.log(` ${statusIcon} ${d.label.padEnd(20)} ${d.status.padEnd(10)} ${txIdShort.padEnd(16)} ${ago}`);
|
|
64
|
+
total++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log(`
|
|
68
|
+
Total: ${total} deployments across ${Object.keys(byNetwork).length} networks`);
|
|
69
|
+
}
|
|
70
|
+
async function inspectDeployment(opts) {
|
|
71
|
+
const rootDir = process.cwd();
|
|
72
|
+
const record = await loadDeployment(rootDir, opts.network, opts.label);
|
|
73
|
+
if (!record) {
|
|
74
|
+
throw new Error(`Deployment '${opts.label}' not found on network '${opts.network}'.`);
|
|
75
|
+
}
|
|
76
|
+
if (opts.json) {
|
|
77
|
+
console.log(JSON.stringify(record, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
UI.header(`Deployment: ${record.label}`);
|
|
81
|
+
console.log(` Network: ${record.networkId}`);
|
|
82
|
+
console.log(` Status: ${record.status}`);
|
|
83
|
+
console.log(` TxId: ${record.txId || "(none)"}`);
|
|
84
|
+
console.log(` Plan artifact: ${record.planArtifactId || "(none)"}`);
|
|
85
|
+
console.log(` Receipt: ${record.receiptArtifactId || "(none)"}`);
|
|
86
|
+
console.log(` Deployed at: ${record.deployedAt}`);
|
|
87
|
+
console.log(` Content hash: ${record.contentHash}`);
|
|
88
|
+
if (record.notes) console.log(` Notes: ${record.notes}`);
|
|
89
|
+
}
|
|
90
|
+
async function verifyDeploymentStatus(opts) {
|
|
91
|
+
const rootDir = process.cwd();
|
|
92
|
+
const record = await loadDeployment(rootDir, opts.network, opts.label);
|
|
93
|
+
if (!record) {
|
|
94
|
+
throw new Error(`Deployment '${opts.label}' not found on network '${opts.network}'.`);
|
|
95
|
+
}
|
|
96
|
+
if (!opts.verify) {
|
|
97
|
+
if (opts.json) console.log(JSON.stringify({ label: record.label, status: record.status }, null, 2));
|
|
98
|
+
else console.log(`Current status: ${record.status}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!record.txId) {
|
|
102
|
+
throw new Error(`Cannot verify deployment '${opts.label}' without a transaction ID.`);
|
|
103
|
+
}
|
|
104
|
+
UI.info(`Checking ${record.label} on ${record.networkId}...`);
|
|
105
|
+
const { config } = await loadHardkasConfig();
|
|
106
|
+
const netTarget = resolveNetworkTarget({ config, network: record.networkId });
|
|
107
|
+
const rpcUrl = netTarget.target.rpcUrl;
|
|
108
|
+
console.log(` RPC: ${rpcUrl || "simulated"}`);
|
|
109
|
+
console.log(` TxId: ${record.txId}`);
|
|
110
|
+
if (record.networkId === "simnet") {
|
|
111
|
+
UI.info(" Simnet deployment \u2014 status preserved.");
|
|
112
|
+
} else if (!rpcUrl) {
|
|
113
|
+
UI.error(" No RPC URL configured for this network.");
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
const client = new JsonWrpcKaspaClient({ rpcUrl });
|
|
117
|
+
const tx = await client.getTransaction(record.txId);
|
|
118
|
+
let newStatus = record.status;
|
|
119
|
+
if (tx) {
|
|
120
|
+
const isAccepted = tx.isAccepted || tx.transaction && tx.transaction.block_hash;
|
|
121
|
+
if (isAccepted) newStatus = "confirmed";
|
|
122
|
+
} else {
|
|
123
|
+
}
|
|
124
|
+
if (newStatus !== record.status) {
|
|
125
|
+
await withLock({ rootDir, name: "artifacts", command: "hardkas deploy status" }, async () => {
|
|
126
|
+
const updated = updateDeploymentStatus(record, newStatus);
|
|
127
|
+
await saveDeployment(rootDir, updated);
|
|
128
|
+
UI.success(` Status updated: ${newStatus}`);
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
UI.info(` Status remains: ${record.status}`);
|
|
132
|
+
}
|
|
133
|
+
await client.close();
|
|
134
|
+
} catch (e) {
|
|
135
|
+
UI.error(` RPC check failed: ${e.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function showDeploymentHistory(opts) {
|
|
140
|
+
const options = {
|
|
141
|
+
...opts.json !== void 0 ? { json: opts.json } : {}
|
|
142
|
+
};
|
|
143
|
+
await listAllDeployments(options);
|
|
144
|
+
}
|
|
145
|
+
function formatAgo(dateStr) {
|
|
146
|
+
const date = new Date(dateStr);
|
|
147
|
+
const now = /* @__PURE__ */ new Date();
|
|
148
|
+
const diffMs = now.getTime() - date.getTime();
|
|
149
|
+
const diffMin = Math.floor(diffMs / 6e4);
|
|
150
|
+
if (diffMin < 1) return "just now";
|
|
151
|
+
if (diffMin < 60) return `${diffMin} min ago`;
|
|
152
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
153
|
+
if (diffHour < 24) return `${diffHour} hours ago`;
|
|
154
|
+
return `${Math.floor(diffHour / 24)} days ago`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export {
|
|
158
|
+
trackDeployment,
|
|
159
|
+
listAllDeployments,
|
|
160
|
+
inspectDeployment,
|
|
161
|
+
verifyDeploymentStatus,
|
|
162
|
+
showDeploymentHistory
|
|
163
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/runners/secrets.ts
|
|
2
|
+
import enquirer from "enquirer";
|
|
3
|
+
var { Password } = enquirer;
|
|
4
|
+
async function acquirePassword(options = {}) {
|
|
5
|
+
if (options.env && process.env[options.env]) {
|
|
6
|
+
return process.env[options.env];
|
|
7
|
+
}
|
|
8
|
+
if (options.stdin) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
let data = "";
|
|
11
|
+
process.stdin.setEncoding("utf8");
|
|
12
|
+
process.stdin.on("data", (chunk) => {
|
|
13
|
+
data += chunk;
|
|
14
|
+
});
|
|
15
|
+
process.stdin.on("end", () => {
|
|
16
|
+
resolve(data.trim());
|
|
17
|
+
});
|
|
18
|
+
process.stdin.on("error", reject);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const prompt = new Password({
|
|
22
|
+
name: "password",
|
|
23
|
+
message: options.message || "Enter password:"
|
|
24
|
+
});
|
|
25
|
+
return await prompt.run();
|
|
26
|
+
}
|
|
27
|
+
async function acquirePrivateKey(options = {}) {
|
|
28
|
+
return acquirePassword({
|
|
29
|
+
...options,
|
|
30
|
+
message: options.message || "Enter private key (hex):"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
acquirePassword,
|
|
36
|
+
acquirePrivateKey
|
|
37
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
UI
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-K7XPWWIO.js";
|
|
4
4
|
|
|
5
5
|
// src/runners/dag-runners.ts
|
|
6
6
|
import {
|
|
@@ -49,7 +49,7 @@ async function runDagSimulateReorg(options) {
|
|
|
49
49
|
if (!forkPointId) throw new Error("Could not find fork point in current path.");
|
|
50
50
|
const forkPoint = state.dag.blocks[forkPointId];
|
|
51
51
|
if (!forkPoint) throw new Error(`Fork point block ${forkPointId} not found in state.`);
|
|
52
|
-
const sideBlockId = `reorg_side_${
|
|
52
|
+
const sideBlockId = `reorg_side_${forkPointId}_0`;
|
|
53
53
|
state.dag.blocks[sideBlockId] = {
|
|
54
54
|
id: sideBlockId,
|
|
55
55
|
parents: [forkPointId],
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inspectDeployment,
|
|
3
|
+
listAllDeployments,
|
|
4
|
+
showDeploymentHistory,
|
|
5
|
+
trackDeployment,
|
|
6
|
+
verifyDeploymentStatus
|
|
7
|
+
} from "./chunk-KA5CAWI2.js";
|
|
8
|
+
import "./chunk-K7XPWWIO.js";
|
|
9
|
+
export {
|
|
10
|
+
inspectDeployment,
|
|
11
|
+
listAllDeployments,
|
|
12
|
+
showDeploymentHistory,
|
|
13
|
+
trackDeployment,
|
|
14
|
+
verifyDeploymentStatus
|
|
15
|
+
};
|