withub-cli 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/LICENSE +201 -0
- package/README.md +41 -0
- package/dist/commands/account.js +109 -0
- package/dist/commands/checkout.js +213 -0
- package/dist/commands/clone.js +263 -0
- package/dist/commands/commit.js +150 -0
- package/dist/commands/diff.js +159 -0
- package/dist/commands/fetch.js +169 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/invite.js +72 -0
- package/dist/commands/list.js +214 -0
- package/dist/commands/plumbing.js +98 -0
- package/dist/commands/pull.js +160 -0
- package/dist/commands/push.js +371 -0
- package/dist/commands/registerCommands.js +183 -0
- package/dist/commands/removeUser.js +63 -0
- package/dist/commands/stub.js +11 -0
- package/dist/commands/transfer.js +46 -0
- package/dist/commands/walrusBlob.js +50 -0
- package/dist/commands/walrusQuilt.js +282 -0
- package/dist/commands/workspace.js +260 -0
- package/dist/index.js +46 -0
- package/dist/lib/config.js +49 -0
- package/dist/lib/constants.js +6 -0
- package/dist/lib/fs.js +154 -0
- package/dist/lib/keys.js +224 -0
- package/dist/lib/manifest.js +37 -0
- package/dist/lib/quilt.js +38 -0
- package/dist/lib/repo.js +70 -0
- package/dist/lib/schema.js +53 -0
- package/dist/lib/seal.js +157 -0
- package/dist/lib/serialize.js +30 -0
- package/dist/lib/state.js +57 -0
- package/dist/lib/suiRepo.js +220 -0
- package/dist/lib/ui.js +51 -0
- package/dist/lib/validate.js +13 -0
- package/dist/lib/walrus.js +237 -0
- package/package.json +57 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.utf8ToVec = utf8ToVec;
|
|
4
|
+
exports.decodeVecAsString = decodeVecAsString;
|
|
5
|
+
exports.createRepository = createRepository;
|
|
6
|
+
exports.updateRepositoryHead = updateRepositoryHead;
|
|
7
|
+
exports.decodeVecAsHex = decodeVecAsHex;
|
|
8
|
+
exports.fetchRepositoryState = fetchRepositoryState;
|
|
9
|
+
exports.fetchRepositoryStateWithRetry = fetchRepositoryStateWithRetry;
|
|
10
|
+
exports.addCollaborator = addCollaborator;
|
|
11
|
+
exports.transferOwnership = transferOwnership;
|
|
12
|
+
exports.removeCollaborator = removeCollaborator;
|
|
13
|
+
const transactions_1 = require("@mysten/sui/transactions");
|
|
14
|
+
const constants_1 = require("./constants");
|
|
15
|
+
function utf8ToVec(input) {
|
|
16
|
+
return Array.from(Buffer.from(input, 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
function decodeVecAsString(raw) {
|
|
19
|
+
if (raw === null || raw === undefined)
|
|
20
|
+
return null;
|
|
21
|
+
if (typeof raw === 'string') {
|
|
22
|
+
if (raw.startsWith('0x')) {
|
|
23
|
+
return Buffer.from(raw.slice(2), 'hex').toString('utf8');
|
|
24
|
+
}
|
|
25
|
+
return raw;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(raw)) {
|
|
28
|
+
if (!raw.length)
|
|
29
|
+
return null;
|
|
30
|
+
if (raw.every((v) => typeof v === 'number')) {
|
|
31
|
+
return Buffer.from(raw).toString('utf8');
|
|
32
|
+
}
|
|
33
|
+
if (raw.length === 1) {
|
|
34
|
+
return decodeVecAsString(raw[0]);
|
|
35
|
+
}
|
|
36
|
+
return Buffer.from(String(raw[0])).toString('utf8');
|
|
37
|
+
}
|
|
38
|
+
if (typeof raw === 'object') {
|
|
39
|
+
const asRec = raw;
|
|
40
|
+
if (asRec.vec !== undefined)
|
|
41
|
+
return decodeVecAsString(asRec.vec);
|
|
42
|
+
if (asRec.fields !== undefined)
|
|
43
|
+
return decodeVecAsString(asRec.fields);
|
|
44
|
+
}
|
|
45
|
+
return String(raw);
|
|
46
|
+
}
|
|
47
|
+
function getSignerAddress(signer) {
|
|
48
|
+
// @ts-ignore Signer is implemented by Ed25519Keypair
|
|
49
|
+
return signer.getPublicKey().toSuiAddress();
|
|
50
|
+
}
|
|
51
|
+
async function createRepository(client, signer, params) {
|
|
52
|
+
const pkg = params.packageId || constants_1.WIT_PACKAGE_ID;
|
|
53
|
+
const mod = params.moduleName || constants_1.WIT_MODULE_NAME;
|
|
54
|
+
const tx = new transactions_1.Transaction();
|
|
55
|
+
tx.setSenderIfNotSet(getSignerAddress(signer));
|
|
56
|
+
tx.moveCall({
|
|
57
|
+
target: `${pkg}::${mod}::create_repo`,
|
|
58
|
+
arguments: [
|
|
59
|
+
tx.pure.vector('u8', utf8ToVec(params.name)),
|
|
60
|
+
tx.pure.vector('u8', utf8ToVec(params.description || '')),
|
|
61
|
+
tx.pure.bool(params.isPrivate),
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
const res = await client.signAndExecuteTransaction({
|
|
65
|
+
signer,
|
|
66
|
+
transaction: tx,
|
|
67
|
+
options: { showEffects: true, showObjectChanges: true }
|
|
68
|
+
});
|
|
69
|
+
const changes = res.objectChanges || [];
|
|
70
|
+
const repoType = `${pkg}::${mod}::Repository`;
|
|
71
|
+
// Find the created Repository object with the correct type
|
|
72
|
+
const repoObj = changes.find((c) => c.type === 'created' && c.objectType === repoType);
|
|
73
|
+
if (!repoObj || !('objectId' in repoObj)) {
|
|
74
|
+
// Fallback logic for older versions
|
|
75
|
+
const created = res?.effects?.created || [];
|
|
76
|
+
// Try to find the Repository by looking for the second shared object if private
|
|
77
|
+
const sharedObjects = created.filter((c) => c?.owner?.Shared !== undefined);
|
|
78
|
+
const fallbackId = sharedObjects[sharedObjects.length - 1]?.reference?.objectId ||
|
|
79
|
+
sharedObjects[sharedObjects.length - 1]?.reference?.object_id;
|
|
80
|
+
if (fallbackId)
|
|
81
|
+
return fallbackId;
|
|
82
|
+
throw new Error(`create_repo did not return a repository object of type ${repoType}`);
|
|
83
|
+
}
|
|
84
|
+
return repoObj.objectId;
|
|
85
|
+
}
|
|
86
|
+
async function updateRepositoryHead(client, signer, params) {
|
|
87
|
+
const pkg = params.packageId || constants_1.WIT_PACKAGE_ID;
|
|
88
|
+
const mod = params.moduleName || constants_1.WIT_MODULE_NAME;
|
|
89
|
+
const tx = new transactions_1.Transaction();
|
|
90
|
+
tx.setSenderIfNotSet(getSignerAddress(signer));
|
|
91
|
+
tx.moveCall({
|
|
92
|
+
target: `${pkg}::${mod}::update_head`,
|
|
93
|
+
arguments: [
|
|
94
|
+
tx.object(params.repoId),
|
|
95
|
+
tx.pure.vector('u8', utf8ToVec(params.commitId)),
|
|
96
|
+
tx.pure.vector('u8', utf8ToVec(params.manifestId)),
|
|
97
|
+
tx.pure.vector('u8', utf8ToVec(params.quiltId)),
|
|
98
|
+
tx.pure.u64(params.expectedVersion),
|
|
99
|
+
tx.pure.option('vector<u8>', params.parentCommit ? utf8ToVec(params.parentCommit) : null),
|
|
100
|
+
],
|
|
101
|
+
});
|
|
102
|
+
await client.signAndExecuteTransaction({ signer, transaction: tx, options: { showEffects: true } });
|
|
103
|
+
}
|
|
104
|
+
function decodeVecAsHex(raw) {
|
|
105
|
+
if (raw === null || raw === undefined)
|
|
106
|
+
return null;
|
|
107
|
+
if (typeof raw === 'string') {
|
|
108
|
+
return raw.startsWith('0x') ? raw : `0x${raw}`;
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(raw)) {
|
|
111
|
+
if (!raw.length)
|
|
112
|
+
return null;
|
|
113
|
+
if (raw.every((v) => typeof v === 'number')) {
|
|
114
|
+
return `0x${Buffer.from(raw).toString('hex')}`;
|
|
115
|
+
}
|
|
116
|
+
if (raw.length === 1) {
|
|
117
|
+
return decodeVecAsHex(raw[0]);
|
|
118
|
+
}
|
|
119
|
+
// Should not happen for vector<u8>
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (typeof raw === 'object') {
|
|
123
|
+
const asRec = raw;
|
|
124
|
+
if (asRec.vec !== undefined)
|
|
125
|
+
return decodeVecAsHex(asRec.vec);
|
|
126
|
+
if (asRec.fields !== undefined)
|
|
127
|
+
return decodeVecAsHex(asRec.fields);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
async function fetchRepositoryState(client, repoId) {
|
|
132
|
+
const resp = await client.getObject({ id: repoId, options: { showContent: true } });
|
|
133
|
+
const data = resp?.data;
|
|
134
|
+
if (!data?.content || data.content.dataType !== 'moveObject') {
|
|
135
|
+
throw new Error('Repository object not found or not a Move object.');
|
|
136
|
+
}
|
|
137
|
+
const fields = data.content.fields || {};
|
|
138
|
+
return {
|
|
139
|
+
repoId,
|
|
140
|
+
owner: fields.owner ? decodeVecAsString(fields.owner) || undefined : undefined,
|
|
141
|
+
headCommit: decodeVecAsString(fields.head_commit),
|
|
142
|
+
headManifest: decodeVecAsString(fields.head_manifest),
|
|
143
|
+
headQuilt: decodeVecAsString(fields.head_quilt),
|
|
144
|
+
version: Number(fields.version || 0),
|
|
145
|
+
sealPolicyId: decodeVecAsHex(fields.seal_policy_id),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async function fetchRepositoryStateWithRetry(client, repoId, attempts = 3, delayMs = 1200) {
|
|
149
|
+
let lastErr;
|
|
150
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
151
|
+
try {
|
|
152
|
+
return await fetchRepositoryState(client, repoId);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
lastErr = err;
|
|
156
|
+
if (i < attempts - 1) {
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw lastErr;
|
|
163
|
+
}
|
|
164
|
+
async function addCollaborator(client, signer, params) {
|
|
165
|
+
const pkg = params.packageId || constants_1.WIT_PACKAGE_ID;
|
|
166
|
+
const mod = params.moduleName || constants_1.WIT_MODULE_NAME;
|
|
167
|
+
const tx = new transactions_1.Transaction();
|
|
168
|
+
tx.setSenderIfNotSet(getSignerAddress(signer));
|
|
169
|
+
if (params.whitelistId) {
|
|
170
|
+
tx.moveCall({
|
|
171
|
+
target: `${pkg}::${mod}::add_private_collaborator`,
|
|
172
|
+
arguments: [tx.object(params.repoId), tx.object(params.whitelistId), tx.pure.address(params.collaborator)],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
tx.moveCall({
|
|
177
|
+
target: `${pkg}::${mod}::add_collaborator`,
|
|
178
|
+
arguments: [tx.object(params.repoId), tx.pure.address(params.collaborator)],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
await client.signAndExecuteTransaction({ signer, transaction: tx, options: { showEffects: true } });
|
|
182
|
+
}
|
|
183
|
+
async function transferOwnership(client, signer, params) {
|
|
184
|
+
const pkg = params.packageId || constants_1.WIT_PACKAGE_ID;
|
|
185
|
+
const mod = params.moduleName || constants_1.WIT_MODULE_NAME;
|
|
186
|
+
const tx = new transactions_1.Transaction();
|
|
187
|
+
tx.setSenderIfNotSet(getSignerAddress(signer));
|
|
188
|
+
if (params.whitelistId) {
|
|
189
|
+
tx.moveCall({
|
|
190
|
+
target: `${pkg}::${mod}::transfer_ownership_private`,
|
|
191
|
+
arguments: [tx.object(params.repoId), tx.object(params.whitelistId), tx.pure.address(params.newOwner)],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
tx.moveCall({
|
|
196
|
+
target: `${pkg}::${mod}::transfer_ownership`,
|
|
197
|
+
arguments: [tx.object(params.repoId), tx.pure.address(params.newOwner)],
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
await client.signAndExecuteTransaction({ signer, transaction: tx, options: { showEffects: true } });
|
|
201
|
+
}
|
|
202
|
+
async function removeCollaborator(client, signer, params) {
|
|
203
|
+
const pkg = params.packageId || constants_1.WIT_PACKAGE_ID;
|
|
204
|
+
const mod = params.moduleName || constants_1.WIT_MODULE_NAME;
|
|
205
|
+
const tx = new transactions_1.Transaction();
|
|
206
|
+
tx.setSenderIfNotSet(getSignerAddress(signer));
|
|
207
|
+
if (params.whitelistId) {
|
|
208
|
+
tx.moveCall({
|
|
209
|
+
target: `${pkg}::${mod}::remove_private_collaborator`,
|
|
210
|
+
arguments: [tx.object(params.repoId), tx.object(params.whitelistId), tx.pure.address(params.collaborator)],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
tx.moveCall({
|
|
215
|
+
target: `${pkg}::${mod}::remove_collaborator`,
|
|
216
|
+
arguments: [tx.object(params.repoId), tx.pure.address(params.collaborator)],
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
await client.signAndExecuteTransaction({ signer, transaction: tx, options: { showEffects: true } });
|
|
220
|
+
}
|
package/dist/lib/ui.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.colors = void 0;
|
|
7
|
+
exports.setColorsEnabled = setColorsEnabled;
|
|
8
|
+
exports.colorsEnabled = colorsEnabled;
|
|
9
|
+
exports.theme = theme;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
let enabled = isColorDefaultOn();
|
|
12
|
+
let chalkInstance = new chalk_1.default.Instance({ level: enabled ? 3 : 0 });
|
|
13
|
+
function isColorDefaultOn() {
|
|
14
|
+
return (process.env.WIT_NO_COLOR === undefined &&
|
|
15
|
+
process.env.NO_COLOR === undefined &&
|
|
16
|
+
(process.env.FORCE_COLOR === undefined || process.env.FORCE_COLOR !== '0'));
|
|
17
|
+
}
|
|
18
|
+
function updateInstance() {
|
|
19
|
+
chalkInstance = new chalk_1.default.Instance({ level: enabled ? 3 : 0 });
|
|
20
|
+
}
|
|
21
|
+
const wrap = (style) => (text) => style(chalkInstance)(text);
|
|
22
|
+
function setColorsEnabled(flag) {
|
|
23
|
+
enabled = flag;
|
|
24
|
+
updateInstance();
|
|
25
|
+
}
|
|
26
|
+
function colorsEnabled() {
|
|
27
|
+
return enabled;
|
|
28
|
+
}
|
|
29
|
+
// Access to the underlying chalk instance (for chainable styles if needed)
|
|
30
|
+
function theme() {
|
|
31
|
+
return chalkInstance;
|
|
32
|
+
}
|
|
33
|
+
exports.colors = {
|
|
34
|
+
red: wrap((c) => c.red),
|
|
35
|
+
green: wrap((c) => c.green),
|
|
36
|
+
yellow: wrap((c) => c.yellow),
|
|
37
|
+
blue: wrap((c) => c.blue),
|
|
38
|
+
cyan: wrap((c) => c.cyan),
|
|
39
|
+
gray: wrap((c) => c.gray),
|
|
40
|
+
bold: wrap((c) => c.bold),
|
|
41
|
+
header: wrap((c) => c.bold.white),
|
|
42
|
+
commit: wrap((c) => c.yellow),
|
|
43
|
+
author: wrap((c) => c.blue),
|
|
44
|
+
date: wrap((c) => c.green),
|
|
45
|
+
hash: wrap((c) => c.yellow),
|
|
46
|
+
added: wrap((c) => c.green),
|
|
47
|
+
deleted: wrap((c) => c.red),
|
|
48
|
+
modified: wrap((c) => c.red),
|
|
49
|
+
staged: wrap((c) => c.green),
|
|
50
|
+
untracked: wrap((c) => c.red),
|
|
51
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateManifest = validateManifest;
|
|
4
|
+
const schema_1 = require("./schema");
|
|
5
|
+
const manifest_1 = require("./manifest");
|
|
6
|
+
function validateManifest(manifest) {
|
|
7
|
+
const parsed = schema_1.ManifestSchema.parse(manifest);
|
|
8
|
+
const computed = (0, manifest_1.computeRootHash)(parsed.files);
|
|
9
|
+
if (computed !== parsed.root_hash) {
|
|
10
|
+
throw new Error(`Manifest root_hash mismatch (expected ${parsed.root_hash}, computed ${computed})`);
|
|
11
|
+
}
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WalrusService = void 0;
|
|
7
|
+
exports.resolveWalrusConfig = resolveWalrusConfig;
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const walrus_1 = require("@mysten/walrus");
|
|
13
|
+
// Walrus SDK expects a global crypto; Node 18+ provides crypto.webcrypto.
|
|
14
|
+
if (typeof globalThis.crypto === 'undefined' && crypto_1.default?.webcrypto) {
|
|
15
|
+
globalThis.crypto = crypto_1.default.webcrypto;
|
|
16
|
+
}
|
|
17
|
+
const DEFAULT_NETWORK = 'testnet';
|
|
18
|
+
const DEFAULT_RELAYS = {
|
|
19
|
+
mainnet: ['https://upload-relay.mainnet.walrus.space'],
|
|
20
|
+
testnet: ['https://upload-relay.testnet.walrus.space'],
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_AGGREGATORS = {
|
|
23
|
+
mainnet: 'https://aggregator.walrus.space',
|
|
24
|
+
testnet: 'https://aggregator.walrus-testnet.walrus.space',
|
|
25
|
+
};
|
|
26
|
+
const DEFAULT_SUI_RPC = {
|
|
27
|
+
mainnet: 'https://fullnode.mainnet.sui.io:443',
|
|
28
|
+
testnet: 'https://fullnode.testnet.sui.io:443',
|
|
29
|
+
};
|
|
30
|
+
async function readJsonIfExists(file) {
|
|
31
|
+
try {
|
|
32
|
+
const raw = await promises_1.default.readFile(file, 'utf8');
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err?.code === 'ENOENT')
|
|
37
|
+
return null;
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function normalizeNetwork(network) {
|
|
42
|
+
if (network === 'mainnet')
|
|
43
|
+
return 'mainnet';
|
|
44
|
+
return 'testnet';
|
|
45
|
+
}
|
|
46
|
+
function pickRelays(network, repoCfg, globalCfg) {
|
|
47
|
+
const fromRepo = repoCfg?.relays?.filter(Boolean) ?? [];
|
|
48
|
+
if (fromRepo.length)
|
|
49
|
+
return fromRepo;
|
|
50
|
+
const fromGlobal = globalCfg?.relays?.filter(Boolean) ?? [];
|
|
51
|
+
if (fromGlobal.length)
|
|
52
|
+
return fromGlobal;
|
|
53
|
+
return DEFAULT_RELAYS[network];
|
|
54
|
+
}
|
|
55
|
+
function pickPrimaryRelay(network, relays, repoCfg, globalCfg) {
|
|
56
|
+
if (repoCfg?.upload_relay)
|
|
57
|
+
return repoCfg.upload_relay;
|
|
58
|
+
if (repoCfg?.uploadRelay)
|
|
59
|
+
return repoCfg.uploadRelay;
|
|
60
|
+
if (globalCfg?.upload_relay)
|
|
61
|
+
return globalCfg.upload_relay;
|
|
62
|
+
if (globalCfg?.uploadRelay)
|
|
63
|
+
return globalCfg.uploadRelay;
|
|
64
|
+
return relays[0] || DEFAULT_RELAYS[network][0];
|
|
65
|
+
}
|
|
66
|
+
function pickSuiRpc(network, repoCfg, globalCfg) {
|
|
67
|
+
const repoRpc = repoCfg?.sui_rpc || repoCfg?.suiRpc;
|
|
68
|
+
if (repoRpc)
|
|
69
|
+
return repoRpc;
|
|
70
|
+
const globalRpc = globalCfg?.sui_rpc || globalCfg?.suiRpc;
|
|
71
|
+
if (globalRpc)
|
|
72
|
+
return globalRpc;
|
|
73
|
+
return DEFAULT_SUI_RPC[network];
|
|
74
|
+
}
|
|
75
|
+
function pickAggregatorHost(network, repoCfg, globalCfg) {
|
|
76
|
+
if (repoCfg?.aggregator)
|
|
77
|
+
return repoCfg.aggregator;
|
|
78
|
+
if (globalCfg?.aggregator)
|
|
79
|
+
return globalCfg.aggregator;
|
|
80
|
+
return DEFAULT_AGGREGATORS[network];
|
|
81
|
+
}
|
|
82
|
+
async function resolveWalrusConfig(cwd = process.cwd()) {
|
|
83
|
+
const witDir = path_1.default.join(cwd, '.wit');
|
|
84
|
+
const repoCfg = await readJsonIfExists(path_1.default.join(witDir, 'config.json'));
|
|
85
|
+
if (!repoCfg) {
|
|
86
|
+
throw new Error('Not a wit repository (missing .wit/config.json). Run `wit init` first.');
|
|
87
|
+
}
|
|
88
|
+
const globalCfg = await readJsonIfExists(path_1.default.join(os_1.default.homedir(), '.witconfig'));
|
|
89
|
+
const network = normalizeNetwork(repoCfg.network || globalCfg?.network || DEFAULT_NETWORK);
|
|
90
|
+
const relays = pickRelays(network, repoCfg, globalCfg);
|
|
91
|
+
const primaryRelay = pickPrimaryRelay(network, relays, repoCfg, globalCfg);
|
|
92
|
+
const suiRpcUrl = pickSuiRpc(network, repoCfg, globalCfg);
|
|
93
|
+
const aggregatorHost = pickAggregatorHost(network, repoCfg, globalCfg);
|
|
94
|
+
return { network, relays, primaryRelay, suiRpcUrl, aggregatorHost };
|
|
95
|
+
}
|
|
96
|
+
function buildClientConfig(resolved) {
|
|
97
|
+
return {
|
|
98
|
+
network: resolved.network,
|
|
99
|
+
suiRpcUrl: resolved.suiRpcUrl,
|
|
100
|
+
uploadRelay: {
|
|
101
|
+
host: resolved.primaryRelay,
|
|
102
|
+
// Relay requires tip headers (nonce/tx_id) even if tip is 0.
|
|
103
|
+
// We set a max tip to enable this behavior.
|
|
104
|
+
sendTip: { max: 1000 },
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Minimal Walrus client wrapper (single relay) for Stage 2 v1.
|
|
110
|
+
* - Resolves config from .wit/config.json + ~/.witconfig + defaults
|
|
111
|
+
* - Lazily constructs WalrusClient
|
|
112
|
+
* - Uses Aggregator for reads when available (faster than direct relay)
|
|
113
|
+
*/
|
|
114
|
+
class WalrusService {
|
|
115
|
+
constructor(config) {
|
|
116
|
+
this.config = config;
|
|
117
|
+
this.client = null;
|
|
118
|
+
}
|
|
119
|
+
static async fromRepo(cwd = process.cwd()) {
|
|
120
|
+
const resolved = await resolveWalrusConfig(cwd);
|
|
121
|
+
return new WalrusService(resolved);
|
|
122
|
+
}
|
|
123
|
+
getResolvedConfig() {
|
|
124
|
+
return this.config;
|
|
125
|
+
}
|
|
126
|
+
getClient() {
|
|
127
|
+
if (!this.client) {
|
|
128
|
+
this.client = new walrus_1.WalrusClient(buildClientConfig(this.config));
|
|
129
|
+
}
|
|
130
|
+
return this.client;
|
|
131
|
+
}
|
|
132
|
+
async readBlob(blobId) {
|
|
133
|
+
return this.readBlobFast(blobId);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Fast path: prefer Aggregator for reads, fallback to relay SDK
|
|
137
|
+
*/
|
|
138
|
+
async readBlobFast(blobId) {
|
|
139
|
+
if (this.config.aggregatorHost) {
|
|
140
|
+
try {
|
|
141
|
+
return await fetchBlobFromAggregator(this.config.aggregatorHost, blobId);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
// fall back to relay
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return this.getClient().readBlob({ blobId });
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Fetch multiple blobs concurrently (fast path via Aggregator).
|
|
151
|
+
*/
|
|
152
|
+
async readBlobs(ids, concurrency = 6) {
|
|
153
|
+
if (!ids.length)
|
|
154
|
+
return [];
|
|
155
|
+
if (this.config.aggregatorHost) {
|
|
156
|
+
try {
|
|
157
|
+
return await fetchMany(ids, concurrency, (id) => fetchBlobFromAggregator(this.config.aggregatorHost, id));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// fall through to relay fetch below
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const files = await this.getClient().getFiles({ ids });
|
|
164
|
+
return Promise.all(files.map((f) => f.bytes()));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Read a file from a quilt using quiltId + identifier (fast path via Aggregator).
|
|
168
|
+
*/
|
|
169
|
+
async readQuiltFile(quiltId, identifier) {
|
|
170
|
+
if (this.config.aggregatorHost) {
|
|
171
|
+
try {
|
|
172
|
+
return await fetchQuiltFileFromAggregator(this.config.aggregatorHost, quiltId, identifier);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// fall back to relay
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const blob = await this.getClient().getBlob({ blobId: quiltId });
|
|
179
|
+
const files = await blob.files({ identifiers: [identifier] });
|
|
180
|
+
if (files.length) {
|
|
181
|
+
return files[0].bytes();
|
|
182
|
+
}
|
|
183
|
+
// last resort: treat identifier as blob id
|
|
184
|
+
const alt = await this.getClient().getFiles({ ids: [identifier] });
|
|
185
|
+
if (alt.length) {
|
|
186
|
+
return alt[0].bytes();
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`Identifier not found in quilt: ${identifier}`);
|
|
189
|
+
}
|
|
190
|
+
async writeBlob(params) {
|
|
191
|
+
const { blob, signer, epochs, deletable = true, owner, attributes } = params;
|
|
192
|
+
return this.getClient().writeBlob({ blob, signer, epochs, deletable, owner, attributes });
|
|
193
|
+
}
|
|
194
|
+
async writeQuilt(params) {
|
|
195
|
+
const { blobs, signer, epochs, deletable = true } = params;
|
|
196
|
+
const res = await this.getClient().writeQuilt({ blobs, signer, epochs, deletable });
|
|
197
|
+
// Walrus returns both quilt index info and blobId; use blobId as quiltId handle.
|
|
198
|
+
return { quiltId: res.blobId, blobId: res.blobId };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
exports.WalrusService = WalrusService;
|
|
202
|
+
async function fetchBlobFromAggregator(host, blobId) {
|
|
203
|
+
const base = host.endsWith('/') ? host.slice(0, -1) : host;
|
|
204
|
+
const res = await fetch(`${base}/v1/blobs/${encodeURIComponent(blobId)}`);
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
throw new Error(`Aggregator fetch failed: ${res.status} ${res.statusText}`);
|
|
207
|
+
}
|
|
208
|
+
const buf = await res.arrayBuffer();
|
|
209
|
+
return new Uint8Array(buf);
|
|
210
|
+
}
|
|
211
|
+
async function fetchQuiltFileFromAggregator(host, quiltId, identifier) {
|
|
212
|
+
const base = host.endsWith('/') ? host.slice(0, -1) : host;
|
|
213
|
+
const pathId = encodeURIComponent(quiltId);
|
|
214
|
+
// identifier may contain slashes; encodeURIComponent is sufficient for path segment
|
|
215
|
+
const pathIdent = encodeURIComponent(identifier);
|
|
216
|
+
const res = await fetch(`${base}/v1/blobs/by-quilt-id/${pathId}/${pathIdent}`);
|
|
217
|
+
if (!res.ok) {
|
|
218
|
+
throw new Error(`Aggregator quilt fetch failed: ${res.status} ${res.statusText}`);
|
|
219
|
+
}
|
|
220
|
+
const buf = await res.arrayBuffer();
|
|
221
|
+
return new Uint8Array(buf);
|
|
222
|
+
}
|
|
223
|
+
async function fetchMany(items, concurrency, fn) {
|
|
224
|
+
const results = new Array(items.length);
|
|
225
|
+
let index = 0;
|
|
226
|
+
const workers = new Array(Math.min(concurrency, items.length)).fill(null).map(async () => {
|
|
227
|
+
while (true) {
|
|
228
|
+
const i = index;
|
|
229
|
+
if (i >= items.length)
|
|
230
|
+
break;
|
|
231
|
+
index += 1;
|
|
232
|
+
results[i] = await fn(items[i]);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
await Promise.all(workers);
|
|
236
|
+
return results;
|
|
237
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "withub-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "wit CLI (Commander + Ink) skeleton",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/CatKevin/wit.git",
|
|
8
|
+
"directory": "wit/cli"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/CatKevin/wit#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/CatKevin/wit/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": "CatKevin",
|
|
15
|
+
"bin": {
|
|
16
|
+
"wit": "dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"test:smoke": "NO_COLOR=1 bash scripts/smoke.sh",
|
|
22
|
+
"prepublishOnly": "npm run build && npm run test:smoke"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@mysten/seal": "^0.9.4",
|
|
26
|
+
"@mysten/sui": "^1.45.0",
|
|
27
|
+
"@mysten/walrus": "^0.8.4",
|
|
28
|
+
"chalk": "^4.1.2",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"diff": "^5.2.0",
|
|
31
|
+
"ignore": "^5.3.1",
|
|
32
|
+
"ink": "^4.4.1",
|
|
33
|
+
"isbinaryfile": "^5.0.2",
|
|
34
|
+
"react": "^18.2.0",
|
|
35
|
+
"zod": "^3.23.8"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/diff": "^5.2.0",
|
|
39
|
+
"@types/node": "^20.12.12",
|
|
40
|
+
"@types/react": "^18.2.46",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"typescript": "^5.4.5"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"license": "Apache-2.0",
|
|
48
|
+
"files": [
|
|
49
|
+
"dist/**",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE",
|
|
52
|
+
"package.json"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
}
|
|
57
|
+
}
|