withub-cli 0.1.0 → 0.2.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/README.md +74 -26
- package/dist/commands/account.js +262 -63
- package/dist/commands/chain.js +35 -0
- package/dist/commands/checkout.js +137 -16
- package/dist/commands/clone.js +64 -7
- package/dist/commands/commit.js +4 -16
- package/dist/commands/fetch.js +76 -4
- package/dist/commands/init.js +75 -13
- package/dist/commands/invite.js +68 -53
- package/dist/commands/ipfsCar.js +98 -0
- package/dist/commands/lighthouse.js +97 -0
- package/dist/commands/lighthouseDownload.js +58 -0
- package/dist/commands/lighthousePin.js +62 -0
- package/dist/commands/pull.js +2 -1
- package/dist/commands/push.js +224 -8
- package/dist/commands/registerCommands.js +108 -2
- package/dist/commands/remove-user.js +46 -0
- package/dist/commands/removeUser.js +30 -1
- package/dist/index.js +15 -0
- package/dist/lib/chain.js +72 -0
- package/dist/lib/crypto.js +62 -0
- package/dist/lib/evmClone.js +255 -0
- package/dist/lib/evmKeys.js +218 -0
- package/dist/lib/evmProvider.js +88 -0
- package/dist/lib/evmRepo.js +192 -0
- package/dist/lib/ipfsCar.js +132 -0
- package/dist/lib/keccak.js +125 -0
- package/dist/lib/keys.js +102 -37
- package/dist/lib/lighthouse.js +661 -0
- package/dist/lib/lit.js +165 -0
- package/dist/lib/manifest.js +22 -4
- package/dist/lib/repo.js +94 -0
- package/dist/lib/schema.js +26 -6
- package/dist/lib/walrus.js +11 -1
- package/package.json +18 -2
|
@@ -17,12 +17,17 @@ const serialize_1 = require("../lib/serialize");
|
|
|
17
17
|
const repo_1 = require("../lib/repo");
|
|
18
18
|
const seal_1 = require("../lib/seal");
|
|
19
19
|
const keys_1 = require("../lib/keys");
|
|
20
|
+
const lit_1 = require("../lib/lit");
|
|
21
|
+
const crypto_1 = require("../lib/crypto");
|
|
22
|
+
const lighthouse_1 = require("../lib/lighthouse");
|
|
23
|
+
const evmProvider_1 = require("../lib/evmProvider");
|
|
20
24
|
const WIT_DIR = '.wit';
|
|
21
25
|
async function checkoutAction(commitId) {
|
|
22
26
|
const witPath = await requireWitDir();
|
|
23
27
|
const repoCfg = await (0, repo_1.readRepoConfig)(witPath);
|
|
24
28
|
const commit = await (0, state_1.readCommitById)(witPath, commitId);
|
|
25
|
-
const
|
|
29
|
+
const sealPolicyId = (0, repo_1.resolveSuiSealPolicyId)(repoCfg);
|
|
30
|
+
const files = await resolveFilesForCommit(witPath, commit, sealPolicyId, repoCfg);
|
|
26
31
|
if (!files) {
|
|
27
32
|
// Friendly message already printed inside resolveFilesForCommit/ensureBlobsFromManifest
|
|
28
33
|
// eslint-disable-next-line no-console
|
|
@@ -84,11 +89,11 @@ function safeJoin(base, rel) {
|
|
|
84
89
|
}
|
|
85
90
|
return path_1.default.join(base, norm);
|
|
86
91
|
}
|
|
87
|
-
async function resolveFilesForCommit(witPath, commit, sealPolicyId) {
|
|
92
|
+
async function resolveFilesForCommit(witPath, commit, sealPolicyId, repoCfg) {
|
|
88
93
|
if (commit.tree?.files && Object.keys(commit.tree.files).length) {
|
|
89
94
|
return commit.tree.files;
|
|
90
95
|
}
|
|
91
|
-
const manifestId = commit.tree?.manifest_id;
|
|
96
|
+
const manifestId = commit.tree?.manifest_id || commit.tree?.manifest_cid;
|
|
92
97
|
if (!manifestId) {
|
|
93
98
|
throw new Error('Commit has no file list or manifest_id; cannot checkout.');
|
|
94
99
|
}
|
|
@@ -100,7 +105,7 @@ async function resolveFilesForCommit(witPath, commit, sealPolicyId) {
|
|
|
100
105
|
if (computedRoot !== commit.tree.root_hash) {
|
|
101
106
|
throw new Error('Commit root_hash does not match manifest.');
|
|
102
107
|
}
|
|
103
|
-
const ok = await ensureBlobsFromManifest(witPath, manifest, sealPolicyId);
|
|
108
|
+
const ok = await ensureBlobsFromManifest(witPath, manifest, sealPolicyId, repoCfg);
|
|
104
109
|
if (!ok) {
|
|
105
110
|
return null;
|
|
106
111
|
}
|
|
@@ -117,16 +122,35 @@ async function loadManifest(witPath, manifestId) {
|
|
|
117
122
|
throw err;
|
|
118
123
|
}
|
|
119
124
|
// fetch from walrus
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
// fetch from walrus or lighthouse
|
|
126
|
+
let buf;
|
|
127
|
+
try {
|
|
128
|
+
const walrusSvc = await walrus_1.WalrusService.fromRepo();
|
|
129
|
+
// eslint-disable-next-line no-console
|
|
130
|
+
console.log(ui_1.colors.cyan(`Fetching manifest ${manifestId} from Walrus...`));
|
|
131
|
+
buf = Buffer.from(await walrusSvc.readBlob(manifestId));
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// If Walrus fails or not configured, try Lighthouse (Mantle fallback)
|
|
135
|
+
// eslint-disable-next-line no-console
|
|
136
|
+
console.log(ui_1.colors.cyan(`Fetching manifest ${manifestId} from Lighthouse...`));
|
|
137
|
+
const res = await (0, lighthouse_1.downloadFromLighthouseGateway)(manifestId, { verify: false });
|
|
138
|
+
buf = Buffer.from(res.bytes);
|
|
139
|
+
}
|
|
124
140
|
const manifest = schema_1.ManifestSchema.parse(JSON.parse(buf.toString('utf8')));
|
|
125
141
|
await promises_1.default.mkdir(path_1.default.dirname(file), { recursive: true });
|
|
126
142
|
await promises_1.default.writeFile(file, buf.toString('utf8'), 'utf8');
|
|
127
143
|
return manifest;
|
|
128
144
|
}
|
|
129
|
-
async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
|
|
145
|
+
async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId, repoCfg) {
|
|
146
|
+
if (repoCfg.chain === 'mantle') {
|
|
147
|
+
return ensureBlobsMantle(witPath, manifest, repoCfg);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
return ensureBlobsSui(witPath, manifest, sealPolicyId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function ensureBlobsSui(witPath, manifest, sealPolicyId) {
|
|
130
154
|
const entries = Object.entries(manifest.files);
|
|
131
155
|
const missing = [];
|
|
132
156
|
for (const [rel, meta] of entries) {
|
|
@@ -137,10 +161,14 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
|
|
|
137
161
|
}
|
|
138
162
|
if (!missing.length)
|
|
139
163
|
return true;
|
|
140
|
-
const walrusSvc = await walrus_1.WalrusService.fromRepo();
|
|
141
164
|
// eslint-disable-next-line no-console
|
|
142
165
|
console.log(ui_1.colors.cyan(`Fetching ${missing.length} missing blobs from Walrus...`));
|
|
143
|
-
|
|
166
|
+
let walrusSvc = null;
|
|
167
|
+
try {
|
|
168
|
+
walrusSvc = await walrus_1.WalrusService.fromRepo();
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
// Setup for decryption
|
|
144
172
|
let signerInfo = null;
|
|
145
173
|
let suiClient = null;
|
|
146
174
|
const hasEncrypted = entries.some(([, meta]) => meta.enc);
|
|
@@ -150,8 +178,10 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
|
|
|
150
178
|
suiClient = new client_1.SuiClient({ url: resolved.suiRpcUrl });
|
|
151
179
|
}
|
|
152
180
|
for (const { rel, meta } of missing) {
|
|
181
|
+
if (!walrusSvc)
|
|
182
|
+
throw new Error('Walrus service not initialized');
|
|
153
183
|
const data = await fetchFileBytes(walrusSvc, manifest, rel, meta);
|
|
154
|
-
let plain;
|
|
184
|
+
let plain = data;
|
|
155
185
|
try {
|
|
156
186
|
if (meta.enc) {
|
|
157
187
|
if (!signerInfo || !suiClient) {
|
|
@@ -168,13 +198,10 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
|
|
|
168
198
|
};
|
|
169
199
|
plain = await (0, seal_1.decryptWithSeal)(data, encMeta, signerInfo.signer, suiClient);
|
|
170
200
|
}
|
|
171
|
-
else {
|
|
172
|
-
plain = data;
|
|
173
|
-
}
|
|
174
201
|
}
|
|
175
202
|
catch (err) {
|
|
176
203
|
// eslint-disable-next-line no-console
|
|
177
|
-
console.log(ui_1.colors.red(
|
|
204
|
+
console.log(ui_1.colors.red(`❌ Access Denied or Decryption Failed for ${rel}: ${err.message}`));
|
|
178
205
|
return false;
|
|
179
206
|
}
|
|
180
207
|
const hash = (0, serialize_1.sha256Base64)(plain);
|
|
@@ -187,6 +214,95 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
|
|
|
187
214
|
}
|
|
188
215
|
return true;
|
|
189
216
|
}
|
|
217
|
+
async function ensureBlobsMantle(witPath, manifest, repoCfg) {
|
|
218
|
+
const entries = Object.entries(manifest.files);
|
|
219
|
+
const missing = [];
|
|
220
|
+
for (const [rel, meta] of entries) {
|
|
221
|
+
const buf = await (0, fs_1.readBlob)(witPath, meta.hash);
|
|
222
|
+
if (!buf) {
|
|
223
|
+
missing.push({ rel, meta });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!missing.length)
|
|
227
|
+
return true;
|
|
228
|
+
// eslint-disable-next-line no-console
|
|
229
|
+
console.log(ui_1.colors.cyan(`Fetching ${missing.length} missing blobs (Lighthouse/Mantle)...`));
|
|
230
|
+
let litService = null;
|
|
231
|
+
let authSig = null;
|
|
232
|
+
let mantleSigner = null;
|
|
233
|
+
const hasEncrypted = entries.some(([, meta]) => meta.enc);
|
|
234
|
+
if (hasEncrypted) {
|
|
235
|
+
litService = new lit_1.LitService();
|
|
236
|
+
mantleSigner = await (0, evmProvider_1.loadMantleSigner)();
|
|
237
|
+
}
|
|
238
|
+
for (const { rel, meta } of missing) {
|
|
239
|
+
let data;
|
|
240
|
+
if (meta.cid) { // Lighthouse/IPFS
|
|
241
|
+
const res = await (0, lighthouse_1.downloadFromLighthouseGateway)(meta.cid, { verify: false });
|
|
242
|
+
data = Buffer.from(res.bytes);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
throw new Error(`Cannot download ${rel}: missing CID for Mantle repo.`);
|
|
246
|
+
}
|
|
247
|
+
let plain = data;
|
|
248
|
+
try {
|
|
249
|
+
if (meta.enc) {
|
|
250
|
+
const encAny = meta.enc;
|
|
251
|
+
if (encAny.alg === 'lit-aes-256-gcm') {
|
|
252
|
+
// Lit Decryption
|
|
253
|
+
if (!litService || !mantleSigner)
|
|
254
|
+
throw new Error('Lit/Mantle env not initialized');
|
|
255
|
+
if (!authSig) {
|
|
256
|
+
// eslint-disable-next-line no-console
|
|
257
|
+
console.log(ui_1.colors.gray(' Generating SIWE AuthSig...'));
|
|
258
|
+
authSig = await litService.getAuthSig(mantleSigner.signer);
|
|
259
|
+
}
|
|
260
|
+
const encMeta = {
|
|
261
|
+
alg: encAny.alg,
|
|
262
|
+
lit_encrypted_key: encAny.lit_encrypted_key,
|
|
263
|
+
unified_access_control_conditions: encAny.unified_access_control_conditions || encAny.access_control_conditions,
|
|
264
|
+
lit_hash: encAny.lit_hash || encAny.enc_hash, // fallback
|
|
265
|
+
iv: encAny.iv,
|
|
266
|
+
tag: encAny.tag
|
|
267
|
+
};
|
|
268
|
+
const sessionKey = await litService.decryptSessionKey(encMeta.lit_encrypted_key, encMeta.lit_hash, encMeta.unified_access_control_conditions, authSig);
|
|
269
|
+
plain = (0, crypto_1.decryptBuffer)({
|
|
270
|
+
ciphertext: data,
|
|
271
|
+
iv: Buffer.from(encMeta.iv, 'hex'),
|
|
272
|
+
authTag: Buffer.from(encMeta.tag, 'hex'),
|
|
273
|
+
}, sessionKey);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
throw new Error(`Unsupported encryption alg on Mantle: ${encAny.alg}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
if (err.message && (err.message.includes('NotAuthorized') || err.message.includes('not authorized'))) {
|
|
282
|
+
// eslint-disable-next-line no-console
|
|
283
|
+
console.log(ui_1.colors.red(`❌ Access Denied: You do not have permission to decrypt ${rel}.`));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// eslint-disable-next-line no-console
|
|
287
|
+
console.log(ui_1.colors.red(`❌ Failed to decrypt ${rel}: ${err.message}`));
|
|
288
|
+
}
|
|
289
|
+
if (litService)
|
|
290
|
+
await litService.disconnect();
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const hash = (0, serialize_1.sha256Base64)(plain);
|
|
294
|
+
if (hash !== meta.hash || plain.length !== meta.size) {
|
|
295
|
+
throw new Error(`Downloaded blob mismatch for ${rel} (hash: ${hash}, expected: ${meta.hash})`);
|
|
296
|
+
}
|
|
297
|
+
const blobPath = path_1.default.join(witPath, 'objects', 'blobs', (0, fs_1.blobFileName)(meta.hash));
|
|
298
|
+
await (0, fs_1.ensureDirForFile)(blobPath);
|
|
299
|
+
await promises_1.default.writeFile(blobPath, plain);
|
|
300
|
+
}
|
|
301
|
+
if (litService) {
|
|
302
|
+
await litService.disconnect();
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
190
306
|
function manifestIdToFile(id) {
|
|
191
307
|
return id.replace(/\//g, '_').replace(/\+/g, '-');
|
|
192
308
|
}
|
|
@@ -209,5 +325,10 @@ async function fetchFileBytes(walrusSvc, manifest, rel, meta) {
|
|
|
209
325
|
}
|
|
210
326
|
return data;
|
|
211
327
|
}
|
|
328
|
+
// Fallback for types that might pass meta with just cid (cached manually handled above usually)
|
|
329
|
+
// But if ensureBlobsFromManifest calls this for a non-walrus file, safeguard:
|
|
330
|
+
if (meta.enc || meta.cid) {
|
|
331
|
+
throw new Error(`Should have been handled by Lighthouse downloader.`);
|
|
332
|
+
}
|
|
212
333
|
throw new Error(`Manifest entry missing Walrus file id for ${rel}`);
|
|
213
334
|
}
|
package/dist/commands/clone.js
CHANGED
|
@@ -18,12 +18,34 @@ const state_1 = require("../lib/state");
|
|
|
18
18
|
const repo_1 = require("../lib/repo");
|
|
19
19
|
const seal_1 = require("../lib/seal");
|
|
20
20
|
const keys_1 = require("../lib/keys");
|
|
21
|
+
const chain_1 = require("../lib/chain");
|
|
22
|
+
const evmClone_1 = require("../lib/evmClone");
|
|
21
23
|
const DEFAULT_RELAYS = ['https://upload-relay.testnet.walrus.space'];
|
|
22
24
|
const DEFAULT_NETWORK = 'testnet';
|
|
23
25
|
async function cloneAction(repoId) {
|
|
24
26
|
if (!repoId) {
|
|
25
27
|
throw new Error('Usage: wit clone <repo_id>');
|
|
26
28
|
}
|
|
29
|
+
const activeChain = await (0, chain_1.readActiveChain)();
|
|
30
|
+
const repoChain = inferRepoChain(repoId, activeChain);
|
|
31
|
+
if (repoId.startsWith('mantle:') || repoChain === 'mantle') {
|
|
32
|
+
try {
|
|
33
|
+
await (0, evmClone_1.cloneFromMantle)(repoId);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.error(ui_1.colors.red(`Clone failed: ${err.message}`));
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (repoChain !== activeChain) {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.error(ui_1.colors.red((0, chain_1.formatChainMismatchMessage)(repoChain, activeChain)));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
27
49
|
// eslint-disable-next-line no-console
|
|
28
50
|
console.log(ui_1.colors.header('Starting clone...'));
|
|
29
51
|
// Prepare .wit layout and config
|
|
@@ -57,7 +79,7 @@ async function cloneAction(repoId) {
|
|
|
57
79
|
try {
|
|
58
80
|
const cfgRaw = await promises_1.default.readFile(path_1.default.join(witPath, 'config.json'), 'utf8');
|
|
59
81
|
const cfg = JSON.parse(cfgRaw);
|
|
60
|
-
cfg
|
|
82
|
+
(0, repo_1.setSuiSealPolicyId)(cfg, onchain.sealPolicyId);
|
|
61
83
|
await (0, repo_1.writeRepoConfig)(witPath, cfg);
|
|
62
84
|
}
|
|
63
85
|
catch {
|
|
@@ -205,14 +227,15 @@ async function ensureLayout(cwd, repoId) {
|
|
|
205
227
|
}
|
|
206
228
|
catch (err) {
|
|
207
229
|
if (err?.code === 'ENOENT') {
|
|
230
|
+
const activeChain = await (0, chain_1.readActiveChain)();
|
|
231
|
+
const repoChain = inferRepoChain(repoId, activeChain);
|
|
232
|
+
const chainConfig = buildChainConfig(repoChain);
|
|
208
233
|
const cfg = {
|
|
209
234
|
repo_name: repoId,
|
|
210
235
|
repo_id: repoId,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
key_alias: 'default',
|
|
215
|
-
seal_policy_id: null,
|
|
236
|
+
chain: repoChain,
|
|
237
|
+
chains: { [repoChain]: chainConfig },
|
|
238
|
+
network: inferNetwork(repoChain),
|
|
216
239
|
created_at: new Date().toISOString(),
|
|
217
240
|
};
|
|
218
241
|
await (0, repo_1.writeRepoConfig)(witPath, cfg);
|
|
@@ -224,13 +247,47 @@ async function ensureLayout(cwd, repoId) {
|
|
|
224
247
|
await promises_1.default.writeFile(path_1.default.join(witPath, 'HEAD'), 'refs/heads/main\n', 'utf8');
|
|
225
248
|
return witPath;
|
|
226
249
|
}
|
|
250
|
+
function inferRepoChain(repoId, fallback) {
|
|
251
|
+
if (repoId.startsWith('mantle:'))
|
|
252
|
+
return 'mantle';
|
|
253
|
+
if (repoId.startsWith('sui:'))
|
|
254
|
+
return 'sui';
|
|
255
|
+
return fallback;
|
|
256
|
+
}
|
|
257
|
+
function inferNetwork(repoChain) {
|
|
258
|
+
if (repoChain === 'mantle')
|
|
259
|
+
return 'testnet';
|
|
260
|
+
else if (repoChain === 'sui')
|
|
261
|
+
return DEFAULT_NETWORK;
|
|
262
|
+
throw new Error(`Unsupported chain "${repoChain}".`);
|
|
263
|
+
}
|
|
264
|
+
function buildChainConfig(repoChain) {
|
|
265
|
+
if (repoChain === 'sui') {
|
|
266
|
+
return {
|
|
267
|
+
author: 'unknown',
|
|
268
|
+
key_alias: 'default',
|
|
269
|
+
network: DEFAULT_NETWORK,
|
|
270
|
+
relays: DEFAULT_RELAYS,
|
|
271
|
+
seal_policy_id: null,
|
|
272
|
+
storage_backend: 'walrus',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
else if (repoChain === 'mantle') {
|
|
276
|
+
return {
|
|
277
|
+
author: 'unknown',
|
|
278
|
+
storage_backend: 'ipfs',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
throw new Error(`Unsupported chain "${repoChain}".`);
|
|
282
|
+
}
|
|
227
283
|
async function ensureHeadFiles(witPath, headCommit) {
|
|
228
284
|
const headRefPath = path_1.default.join(witPath, 'refs', 'heads', 'main');
|
|
229
285
|
await promises_1.default.writeFile(headRefPath, `${headCommit}\n`, 'utf8');
|
|
230
286
|
}
|
|
231
287
|
function parseRemoteCommit(buf) {
|
|
232
288
|
const parsed = JSON.parse(buf.toString('utf8'));
|
|
233
|
-
|
|
289
|
+
const hasManifestRef = Boolean(parsed?.tree?.manifest_id || parsed?.tree?.manifest_cid);
|
|
290
|
+
if (!parsed?.tree?.root_hash || !hasManifestRef) {
|
|
234
291
|
throw new Error('Invalid remote commit object');
|
|
235
292
|
}
|
|
236
293
|
return parsed;
|
package/dist/commands/commit.js
CHANGED
|
@@ -27,8 +27,9 @@ async function commitAction(opts) {
|
|
|
27
27
|
console.warn('Index is empty. Nothing to commit.');
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
30
|
+
const repoCfg = await (0, repo_1.readRepoConfig)(witPath);
|
|
31
|
+
const author = (0, repo_1.resolveChainAuthor)(repoCfg);
|
|
32
|
+
if (!author || author === 'unknown') {
|
|
32
33
|
// eslint-disable-next-line no-console
|
|
33
34
|
console.warn(ui_1.colors.yellow('Warning: author is unknown. Set author in .wit/config.json or ~/.witconfig.'));
|
|
34
35
|
}
|
|
@@ -43,7 +44,7 @@ async function commitAction(opts) {
|
|
|
43
44
|
files: index,
|
|
44
45
|
},
|
|
45
46
|
parent,
|
|
46
|
-
author:
|
|
47
|
+
author: author || 'unknown',
|
|
47
48
|
message,
|
|
48
49
|
timestamp: Math.floor(Date.now() / 1000),
|
|
49
50
|
extras: { patch_id: null, tags: {} },
|
|
@@ -107,19 +108,6 @@ async function requireWitDir() {
|
|
|
107
108
|
throw new Error('Not a wit repository (missing .wit). Run `wit init` first.');
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
|
-
async function readConfig(witPath) {
|
|
111
|
-
const file = path_1.default.join(witPath, 'config.json');
|
|
112
|
-
try {
|
|
113
|
-
const raw = await promises_1.default.readFile(file, 'utf8');
|
|
114
|
-
return JSON.parse(raw);
|
|
115
|
-
}
|
|
116
|
-
catch (err) {
|
|
117
|
-
if (err?.code === 'ENOENT') {
|
|
118
|
-
return {};
|
|
119
|
-
}
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
111
|
async function writeCommitObject(witPath, commitId, serialized) {
|
|
124
112
|
const file = path_1.default.join(witPath, 'objects', 'commits', `${(0, state_1.idToFileName)(commitId)}.json`);
|
|
125
113
|
await promises_1.default.writeFile(file, serialized, 'utf8');
|
package/dist/commands/fetch.js
CHANGED
|
@@ -21,6 +21,14 @@ async function fetchAction() {
|
|
|
21
21
|
if (!repoCfg.repo_id) {
|
|
22
22
|
throw new Error('Missing repo_id in .wit/config.json. Cannot fetch.');
|
|
23
23
|
}
|
|
24
|
+
if (repoCfg.chain === 'mantle') {
|
|
25
|
+
return fetchActionMantle(witPath, repoCfg);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return fetchActionSui(witPath, repoCfg);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function fetchActionSui(witPath, repoCfg) {
|
|
24
32
|
// eslint-disable-next-line no-console
|
|
25
33
|
console.log(ui_1.colors.header('Fetching remote metadata...'));
|
|
26
34
|
const resolved = await (0, walrus_1.resolveWalrusConfig)(process.cwd());
|
|
@@ -32,9 +40,10 @@ async function fetchAction() {
|
|
|
32
40
|
console.log(ui_1.colors.yellow('Remote repository has no head; nothing to fetch.'));
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
const currentPolicyId = (0, repo_1.resolveSuiSealPolicyId)(repoCfg);
|
|
44
|
+
if (onchain.sealPolicyId && onchain.sealPolicyId !== currentPolicyId) {
|
|
45
|
+
(0, repo_1.setSuiSealPolicyId)(repoCfg, onchain.sealPolicyId);
|
|
46
|
+
await (0, repo_1.writeRepoConfig)(witPath, repoCfg);
|
|
38
47
|
}
|
|
39
48
|
// Download manifest and commit for validation/cache
|
|
40
49
|
const manifest = await loadManifestCached(walrusSvc, witPath, onchain.headManifest);
|
|
@@ -71,13 +80,76 @@ async function fetchAction() {
|
|
|
71
80
|
// eslint-disable-next-line no-console
|
|
72
81
|
console.log(`Quilt: ${ui_1.colors.hash(onchain.headQuilt)}`);
|
|
73
82
|
}
|
|
83
|
+
// --------------------------------------------------------------------------
|
|
84
|
+
// MANTLE IMPLEMENTATION
|
|
85
|
+
// --------------------------------------------------------------------------
|
|
86
|
+
const evmProvider_1 = require("../lib/evmProvider");
|
|
87
|
+
const evmRepo_1 = require("../lib/evmRepo");
|
|
88
|
+
const lighthouse_1 = require("../lib/lighthouse");
|
|
89
|
+
const evmClone_1 = require("../lib/evmClone");
|
|
90
|
+
async function fetchActionMantle(witPath, repoCfg) {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log(ui_1.colors.header('Fetching remote metadata (Mantle)...'));
|
|
93
|
+
// 1. Load Contract State
|
|
94
|
+
const signerCtx = await (0, evmProvider_1.loadMantleSigner)();
|
|
95
|
+
const repoService = new evmRepo_1.EvmRepoService(signerCtx);
|
|
96
|
+
let repoId = BigInt(0);
|
|
97
|
+
const repoIdStr = repoCfg.repo_id;
|
|
98
|
+
if (repoIdStr.startsWith('mantle:')) {
|
|
99
|
+
repoId = BigInt(repoIdStr.split(':').pop());
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
repoId = BigInt(repoIdStr);
|
|
103
|
+
}
|
|
104
|
+
const onchain = await repoService.getRepoState(repoId);
|
|
105
|
+
if (!onchain || !onchain.headCommit) {
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(ui_1.colors.yellow('Remote repository has no head; nothing to fetch.'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// 2. Download Head Commit & Manifest
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console.log(ui_1.colors.cyan(`Remote Head: ${onchain.headCommit}`));
|
|
113
|
+
const commitBuf = await downloadBuffer(onchain.headCommit);
|
|
114
|
+
const commit = parseRemoteCommit(commitBuf);
|
|
115
|
+
await cacheJson(path_1.default.join(witPath, 'objects', 'commits', `${(0, state_1.idToFileName)(onchain.headCommit)}.json`), commitBuf.toString('utf8'));
|
|
116
|
+
const manifestCid = commit.tree.manifest_cid || commit.tree.manifest_id;
|
|
117
|
+
if (!manifestCid) {
|
|
118
|
+
throw new Error('Remote commit missing manifest_cid');
|
|
119
|
+
}
|
|
120
|
+
const manifestBuf = await downloadBuffer(manifestCid);
|
|
121
|
+
// Cache manifest
|
|
122
|
+
await cacheJson(path_1.default.join(witPath, 'objects', 'manifests', `${(0, state_1.idToFileName)(manifestCid)}.json`), manifestBuf.toString('utf8'));
|
|
123
|
+
// 3. Sync History (Incremental)
|
|
124
|
+
const map = await readCommitIdMapSafe(witPath);
|
|
125
|
+
await (0, evmClone_1.downloadCommitChainMantle)(onchain.headCommit, witPath, map);
|
|
126
|
+
await (0, state_1.writeCommitIdMap)(witPath, map);
|
|
127
|
+
// 4. Update References
|
|
128
|
+
await (0, repo_1.writeRemoteRef)(witPath, onchain.headCommit);
|
|
129
|
+
await (0, repo_1.writeRemoteState)(witPath, {
|
|
130
|
+
repo_id: repoCfg.repo_id,
|
|
131
|
+
head_commit: onchain.headCommit,
|
|
132
|
+
head_manifest: manifestCid,
|
|
133
|
+
head_quilt: '',
|
|
134
|
+
version: Number(onchain.version),
|
|
135
|
+
});
|
|
136
|
+
// eslint-disable-next-line no-console
|
|
137
|
+
console.log(ui_1.colors.green('Fetch complete (worktree unchanged).'));
|
|
138
|
+
console.log(`Head: ${ui_1.colors.hash(onchain.headCommit)}`);
|
|
139
|
+
}
|
|
140
|
+
async function downloadBuffer(cid) {
|
|
141
|
+
const res = await (0, lighthouse_1.downloadFromLighthouseGateway)(cid, { verify: false });
|
|
142
|
+
return Buffer.from(res.bytes);
|
|
143
|
+
}
|
|
144
|
+
// function downloadCommitChainMantle moved to ../lib/evmClone.ts
|
|
74
145
|
async function cacheJson(filePath, content) {
|
|
75
146
|
await promises_1.default.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
76
147
|
await promises_1.default.writeFile(filePath, content, 'utf8');
|
|
77
148
|
}
|
|
78
149
|
function parseRemoteCommit(buf) {
|
|
79
150
|
const parsed = JSON.parse(buf.toString('utf8'));
|
|
80
|
-
|
|
151
|
+
const hasManifestRef = Boolean(parsed?.tree?.manifest_id || parsed?.tree?.manifest_cid);
|
|
152
|
+
if (!parsed?.tree?.root_hash || !hasManifestRef) {
|
|
81
153
|
throw new Error('Invalid remote commit object');
|
|
82
154
|
}
|
|
83
155
|
return parsed;
|
package/dist/commands/init.js
CHANGED
|
@@ -7,9 +7,12 @@ exports.initAction = initAction;
|
|
|
7
7
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const keys_1 = require("../lib/keys");
|
|
10
|
+
const chain_1 = require("../lib/chain");
|
|
11
|
+
const evmKeys_1 = require("../lib/evmKeys");
|
|
12
|
+
const ui_1 = require("../lib/ui");
|
|
10
13
|
const DEFAULT_RELAYS = ['https://upload-relay.testnet.walrus.space'];
|
|
11
14
|
const DEFAULT_NETWORK = 'testnet';
|
|
12
|
-
const IGNORE_ENTRIES = ['.wit/', '~/.wit/keys', '.env.local', '*.pem', '.wit/seal'];
|
|
15
|
+
const IGNORE_ENTRIES = ['.wit/', '~/.wit/keys', '~/.wit/keys-sui', '~/.wit/keys-evm', '.env.local', '*.pem', '.wit/seal'];
|
|
13
16
|
async function initAction(name, options) {
|
|
14
17
|
const cwd = process.cwd();
|
|
15
18
|
const repoName = name || path_1.default.basename(cwd);
|
|
@@ -17,14 +20,41 @@ async function initAction(name, options) {
|
|
|
17
20
|
await promises_1.default.mkdir(witDir, { recursive: true });
|
|
18
21
|
await ensureLayout(witDir);
|
|
19
22
|
const globalCfg = await readGlobalConfig();
|
|
20
|
-
const
|
|
21
|
-
|
|
23
|
+
const activeChain = await (0, chain_1.readActiveChain)();
|
|
24
|
+
let activeAddress = null;
|
|
25
|
+
if (activeChain === 'mantle') {
|
|
26
|
+
activeAddress = await (0, evmKeys_1.readActiveEvmAddress)();
|
|
27
|
+
}
|
|
28
|
+
else if (activeChain === 'sui') {
|
|
29
|
+
activeAddress = await (0, keys_1.readActiveAddress)();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.error(ui_1.colors.red(`Unsupported chain "${activeChain}".`));
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const repoCfg = buildRepoConfig(repoName, globalCfg, activeAddress, activeChain);
|
|
22
38
|
const wantsPrivate = options?.private || Boolean(options?.sealPolicy || options?.sealSecret);
|
|
23
39
|
if (wantsPrivate) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
if (activeChain === 'sui') {
|
|
41
|
+
// We mark it as pending. The actual policy ID will be generated on-chain during 'wit push'.
|
|
42
|
+
const chainCfg = repoCfg.chains?.[activeChain];
|
|
43
|
+
if (chainCfg) {
|
|
44
|
+
chainCfg.seal_policy_id = 'pending';
|
|
45
|
+
}
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.log('Initialized as PRIVATE repository. Encryption will be enabled on first push.');
|
|
48
|
+
}
|
|
49
|
+
else if (activeChain === 'mantle') {
|
|
50
|
+
repoCfg.isPrivate = true;
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.log('Initialized as PRIVATE repository (Lit Protocol). Encryption will be enabled on first push.');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log('Warning: --private is only supported on Sui and Mantle for now; no seal policy was set.');
|
|
57
|
+
}
|
|
28
58
|
}
|
|
29
59
|
await writeConfigIfMissing(path_1.default.join(witDir, 'config.json'), repoCfg);
|
|
30
60
|
await ensureFile(path_1.default.join(witDir, 'HEAD'), 'refs/heads/main\n');
|
|
@@ -65,18 +95,50 @@ async function readGlobalConfig() {
|
|
|
65
95
|
return {};
|
|
66
96
|
}
|
|
67
97
|
}
|
|
68
|
-
function buildRepoConfig(repoName, globalCfg, activeAddress) {
|
|
98
|
+
function buildRepoConfig(repoName, globalCfg, activeAddress, activeChain) {
|
|
99
|
+
const network = resolveNetwork(activeChain, globalCfg);
|
|
100
|
+
const relays = globalCfg.relays?.length ? globalCfg.relays : DEFAULT_RELAYS;
|
|
101
|
+
const chainConfig = buildChainConfig(activeChain, globalCfg, activeAddress, network, relays);
|
|
69
102
|
return {
|
|
70
103
|
repo_name: repoName,
|
|
71
104
|
repo_id: null,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
key_alias: globalCfg.key_alias || 'default',
|
|
76
|
-
seal_policy_id: null,
|
|
105
|
+
chain: activeChain,
|
|
106
|
+
chains: { [activeChain]: chainConfig },
|
|
107
|
+
network,
|
|
77
108
|
created_at: new Date().toISOString(),
|
|
78
109
|
};
|
|
79
110
|
}
|
|
111
|
+
function buildChainConfig(activeChain, globalCfg, activeAddress, network, relays) {
|
|
112
|
+
if (activeChain === 'sui') {
|
|
113
|
+
return {
|
|
114
|
+
author: activeAddress || globalCfg.author || 'unknown',
|
|
115
|
+
key_alias: globalCfg.key_alias || 'default',
|
|
116
|
+
relays,
|
|
117
|
+
seal_policy_id: null,
|
|
118
|
+
storage_backend: 'walrus',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
else if (activeChain === 'mantle') {
|
|
122
|
+
return {
|
|
123
|
+
author: activeAddress || 'unknown',
|
|
124
|
+
storage_backend: 'ipfs',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// eslint-disable-next-line no-console
|
|
128
|
+
console.error(ui_1.colors.red(`Unsupported chain "${activeChain}".`));
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return {
|
|
131
|
+
author: activeAddress || 'unknown',
|
|
132
|
+
storage_backend: 'ipfs',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function resolveNetwork(activeChain, globalCfg) {
|
|
136
|
+
if (globalCfg.network)
|
|
137
|
+
return globalCfg.network;
|
|
138
|
+
if (activeChain === 'mantle')
|
|
139
|
+
return 'mainnet';
|
|
140
|
+
return DEFAULT_NETWORK; // Defaults to 'testnet' for Sui et al.
|
|
141
|
+
}
|
|
80
142
|
async function writeConfigIfMissing(file, cfg) {
|
|
81
143
|
try {
|
|
82
144
|
await promises_1.default.access(file);
|