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.
@@ -0,0 +1,260 @@
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.statusAction = statusAction;
7
+ exports.addAction = addAction;
8
+ exports.resetAction = resetAction;
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = require("../lib/fs");
12
+ const ui_1 = require("../lib/ui");
13
+ const WIT_DIR = '.wit';
14
+ async function statusAction() {
15
+ const witPath = await requireWitDir();
16
+ const indexPath = path_1.default.join(witPath, 'index');
17
+ const index = await (0, fs_1.readIndex)(indexPath);
18
+ const tracked = new Set(Object.keys(index));
19
+ const ig = await (0, fs_1.buildIgnore)(process.cwd());
20
+ const workspaceFiles = await (0, fs_1.walkFiles)(process.cwd(), ig, process.cwd(), tracked);
21
+ const workspaceMeta = {};
22
+ for (const file of workspaceFiles) {
23
+ const rel = (0, fs_1.pathToPosix)(path_1.default.relative(process.cwd(), file));
24
+ workspaceMeta[rel] = await computeMetaWithCache(file, rel, index);
25
+ }
26
+ const untracked = [];
27
+ const modified = [];
28
+ const deleted = [];
29
+ for (const [rel, meta] of Object.entries(workspaceMeta)) {
30
+ const indexed = index[rel];
31
+ if (!indexed) {
32
+ untracked.push(rel);
33
+ }
34
+ else if (!sameMeta(indexed, meta)) {
35
+ modified.push(rel);
36
+ }
37
+ }
38
+ for (const rel of Object.keys(index)) {
39
+ if (!workspaceMeta[rel]) {
40
+ deleted.push(rel);
41
+ }
42
+ }
43
+ printStatus({ untracked, modified, deleted });
44
+ }
45
+ async function addAction(paths, opts) {
46
+ const witPath = await requireWitDir();
47
+ const indexPath = path_1.default.join(witPath, 'index');
48
+ const index = await (0, fs_1.readIndex)(indexPath);
49
+ const ig = await (0, fs_1.buildIgnore)(process.cwd());
50
+ const targets = await collectTargets(resolveAddTargets(paths, opts));
51
+ const targetRelSet = new Set(targets.map((t) => t.rel));
52
+ const filesToAdd = new Set();
53
+ for (const target of targets) {
54
+ if ((0, fs_1.shouldIgnore)(ig, target.rel, target.isDir)) {
55
+ // eslint-disable-next-line no-console
56
+ console.warn(`Ignored by patterns: ${target.rel}`);
57
+ continue;
58
+ }
59
+ if (target.isDir) {
60
+ const nested = await (0, fs_1.walkFiles)(target.abs, ig, process.cwd());
61
+ nested.forEach((file) => filesToAdd.add(file));
62
+ }
63
+ else {
64
+ filesToAdd.add(target.abs);
65
+ }
66
+ }
67
+ for (const file of filesToAdd) {
68
+ const rel = (0, fs_1.pathToPosix)(path_1.default.relative(process.cwd(), file));
69
+ const meta = await (0, fs_1.computeFileMeta)(file);
70
+ index[rel] = meta;
71
+ await (0, fs_1.ensureBlobFromFile)(witPath, meta.hash, file);
72
+ // eslint-disable-next-line no-console
73
+ console.log(ui_1.colors.added(`added ${rel}`));
74
+ }
75
+ const deletions = [];
76
+ for (const rel of Object.keys(index)) {
77
+ if (!isWithinTargets(rel, targetRelSet))
78
+ continue;
79
+ const absPath = path_1.default.join(process.cwd(), rel);
80
+ try {
81
+ await promises_1.default.stat(absPath);
82
+ }
83
+ catch (err) {
84
+ if (err?.code === 'ENOENT') {
85
+ delete index[rel];
86
+ deletions.push(rel);
87
+ }
88
+ else {
89
+ throw err;
90
+ }
91
+ }
92
+ }
93
+ await (0, fs_1.writeIndex)(indexPath, index);
94
+ deletions.forEach((rel) => console.log(ui_1.colors.deleted(`removed ${rel}`)));
95
+ }
96
+ async function resetAction(paths, opts) {
97
+ const witPath = await requireWitDir();
98
+ const indexPath = path_1.default.join(witPath, 'index');
99
+ const index = await (0, fs_1.readIndex)(indexPath);
100
+ if (opts?.staged === false) {
101
+ return restoreWorktree(paths, index, witPath);
102
+ }
103
+ const relTargets = resolveResetTargets(paths, opts, index);
104
+ if (!relTargets.length) {
105
+ // eslint-disable-next-line no-console
106
+ console.warn('Nothing to unstage (specify paths or --all)');
107
+ return;
108
+ }
109
+ const removed = [];
110
+ const keep = {};
111
+ for (const [rel, meta] of Object.entries(index)) {
112
+ if (matchesTarget(rel, relTargets)) {
113
+ removed.push(rel);
114
+ continue;
115
+ }
116
+ keep[rel] = meta;
117
+ }
118
+ await (0, fs_1.writeIndex)(indexPath, keep);
119
+ if (removed.length === 0) {
120
+ // eslint-disable-next-line no-console
121
+ console.warn('No matching paths were staged.');
122
+ }
123
+ else {
124
+ removed.forEach((rel) => console.log(ui_1.colors.green(`unstaged ${rel}`)));
125
+ }
126
+ }
127
+ async function requireWitDir() {
128
+ const dir = path_1.default.join(process.cwd(), WIT_DIR);
129
+ try {
130
+ await promises_1.default.access(dir);
131
+ return dir;
132
+ }
133
+ catch {
134
+ throw new Error('Not a wit repository (missing .wit). Run `wit init` first.');
135
+ }
136
+ }
137
+ function sameMeta(a, b) {
138
+ return a.hash === b.hash && a.size === b.size && a.mode === b.mode;
139
+ }
140
+ function resolveAddTargets(paths, opts) {
141
+ if ((opts?.all || !paths?.length) && !paths.includes('.')) {
142
+ return ['.'];
143
+ }
144
+ return paths;
145
+ }
146
+ function resolveResetTargets(paths, opts, index) {
147
+ if (opts?.all)
148
+ return Object.keys(index);
149
+ if (!paths?.length)
150
+ return [];
151
+ return paths.map((p) => {
152
+ const abs = path_1.default.isAbsolute(p) ? p : path_1.default.join(process.cwd(), p);
153
+ return (0, fs_1.pathToPosix)(path_1.default.relative(process.cwd(), abs)) || '.';
154
+ });
155
+ }
156
+ async function restoreWorktree(paths, index, witPath) {
157
+ if (!paths?.length) {
158
+ // eslint-disable-next-line no-console
159
+ console.warn('Specify paths to restore (worktree).');
160
+ return;
161
+ }
162
+ const targets = expandTargets(paths, index);
163
+ if (!targets.length) {
164
+ // eslint-disable-next-line no-console
165
+ console.warn('No matching tracked paths to restore.');
166
+ return;
167
+ }
168
+ for (const rel of targets) {
169
+ const meta = index[rel];
170
+ const buf = await (0, fs_1.readBlob)(witPath, meta.hash);
171
+ if (!buf) {
172
+ // eslint-disable-next-line no-console
173
+ console.warn(`Missing blob for ${rel}, cannot restore.`);
174
+ continue;
175
+ }
176
+ const abs = path_1.default.join(process.cwd(), rel);
177
+ await (0, fs_1.ensureDirForFile)(abs);
178
+ await promises_1.default.writeFile(abs, buf);
179
+ const perm = parseInt(meta.mode, 8) & 0o777;
180
+ await promises_1.default.chmod(abs, perm);
181
+ // eslint-disable-next-line no-console
182
+ console.log(ui_1.colors.green(`restored ${rel}`));
183
+ }
184
+ }
185
+ function expandTargets(inputs, index) {
186
+ const result = new Set();
187
+ const targets = inputs.map((p) => {
188
+ const abs = path_1.default.isAbsolute(p) ? p : path_1.default.join(process.cwd(), p);
189
+ return (0, fs_1.pathToPosix)(path_1.default.relative(process.cwd(), abs)) || '.';
190
+ });
191
+ if (!targets.length)
192
+ return [];
193
+ for (const rel of Object.keys(index)) {
194
+ if (targets.some((t) => t === '.' || rel === t || rel.startsWith(`${t}/`))) {
195
+ result.add(rel);
196
+ }
197
+ }
198
+ return Array.from(result).sort();
199
+ }
200
+ async function collectTargets(inputs) {
201
+ const targets = [];
202
+ for (const input of inputs) {
203
+ const abs = path_1.default.isAbsolute(input) ? input : path_1.default.join(process.cwd(), input);
204
+ const stat = await promises_1.default.stat(abs);
205
+ const rel = (0, fs_1.pathToPosix)(path_1.default.relative(process.cwd(), abs)) || '.';
206
+ targets.push({ abs, rel, isDir: stat.isDirectory() });
207
+ }
208
+ return targets;
209
+ }
210
+ function isWithinTargets(rel, targets) {
211
+ if (!targets.size)
212
+ return false;
213
+ if (targets.has('.'))
214
+ return true;
215
+ for (const t of targets) {
216
+ if (rel === t || rel.startsWith(`${t}/`))
217
+ return true;
218
+ }
219
+ return false;
220
+ }
221
+ function matchesTarget(rel, targets) {
222
+ if (!targets.length)
223
+ return false;
224
+ if (targets.includes('.'))
225
+ return true;
226
+ return targets.some((t) => rel === t || rel.startsWith(`${t}/`));
227
+ }
228
+ async function computeMetaWithCache(file, rel, index) {
229
+ const stat = await promises_1.default.stat(file);
230
+ const mode = (0, fs_1.modeToString)(stat.mode);
231
+ const mtime = (0, fs_1.mtimeSec)(stat);
232
+ const indexed = index[rel];
233
+ if (indexed && indexed.size === stat.size && indexed.mode === mode && indexed.mtime === mtime) {
234
+ return indexed;
235
+ }
236
+ return (0, fs_1.computeFileMeta)(file);
237
+ }
238
+ function printStatus(sections) {
239
+ const { untracked, modified, deleted } = sections;
240
+ if (!untracked.length && !modified.length && !deleted.length) {
241
+ // eslint-disable-next-line no-console
242
+ console.log('Nothing to commit, working tree clean.');
243
+ return;
244
+ }
245
+ if (modified.length) {
246
+ // eslint-disable-next-line no-console
247
+ console.log(ui_1.colors.modified('Modified:'));
248
+ modified.forEach((f) => console.log(ui_1.colors.modified(` ${f}`)));
249
+ }
250
+ if (deleted.length) {
251
+ // eslint-disable-next-line no-console
252
+ console.log(ui_1.colors.deleted('Deleted:'));
253
+ deleted.forEach((f) => console.log(ui_1.colors.deleted(` ${f}`)));
254
+ }
255
+ if (untracked.length) {
256
+ // eslint-disable-next-line no-console
257
+ console.log(ui_1.colors.untracked('Untracked:'));
258
+ untracked.forEach((f) => console.log(ui_1.colors.untracked(` ${f}`)));
259
+ }
260
+ }
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.run = run;
8
+ const commander_1 = require("commander");
9
+ const registerCommands_1 = require("./commands/registerCommands");
10
+ const package_json_1 = __importDefault(require("../package.json"));
11
+ // Polyfill for toReversed (Node < 20)
12
+ if (!Array.prototype.toReversed) {
13
+ // eslint-disable-next-line no-extend-native
14
+ Array.prototype.toReversed = function () {
15
+ return [...this].reverse();
16
+ };
17
+ }
18
+ const VERSION = package_json_1.default.version || '0.0.0';
19
+ async function run(argv = process.argv) {
20
+ const program = new commander_1.Command();
21
+ program
22
+ .name('wit')
23
+ .description('wit CLI: single-branch, verifiable, optionally encrypted repo tool backed by Walrus + Sui')
24
+ .version(VERSION);
25
+ (0, registerCommands_1.registerCommands)(program);
26
+ const normalized = normalizeArgs(argv.slice());
27
+ await program.parseAsync(normalized);
28
+ }
29
+ if (require.main === module) {
30
+ run().catch((err) => {
31
+ // eslint-disable-next-line no-console
32
+ console.error(err);
33
+ process.exitCode = 1;
34
+ });
35
+ }
36
+ function normalizeArgs(argv) {
37
+ const args = [...argv];
38
+ const idx = args.findIndex((a) => a === 'checkout');
39
+ if (idx > -1 && args.length > idx + 1) {
40
+ const next = args[idx + 1];
41
+ if (next && next.startsWith('-') && next !== '--') {
42
+ args.splice(idx + 1, 0, '--');
43
+ }
44
+ }
45
+ return args;
46
+ }
@@ -0,0 +1,49 @@
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.DEFAULT_NETWORK = exports.DEFAULT_RELAYS = void 0;
7
+ exports.readGlobalConfig = readGlobalConfig;
8
+ exports.readRepoConfig = readRepoConfig;
9
+ exports.buildWalrusClientOptions = buildWalrusClientOptions;
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const path_1 = __importDefault(require("path"));
12
+ exports.DEFAULT_RELAYS = ['https://relay.walrus-testnet.mystenlabs.com'];
13
+ exports.DEFAULT_NETWORK = 'testnet';
14
+ async function readGlobalConfig() {
15
+ const home = process.env.HOME;
16
+ if (!home)
17
+ return {};
18
+ const file = path_1.default.join(home, '.witconfig');
19
+ try {
20
+ const raw = await promises_1.default.readFile(file, 'utf8');
21
+ return JSON.parse(raw);
22
+ }
23
+ catch (err) {
24
+ if (err?.code === 'ENOENT')
25
+ return {};
26
+ // eslint-disable-next-line no-console
27
+ console.warn(`Warning: could not read ${file}: ${err.message}`);
28
+ return {};
29
+ }
30
+ }
31
+ async function readRepoConfig(witDir) {
32
+ const file = path_1.default.join(witDir, 'config.json');
33
+ const raw = await promises_1.default.readFile(file, 'utf8');
34
+ return JSON.parse(raw);
35
+ }
36
+ function buildWalrusClientOptions(repoCfg, globalCfg) {
37
+ const relays = cleanRelays(repoCfg.relays?.length ? repoCfg.relays : globalCfg.relays) || exports.DEFAULT_RELAYS;
38
+ return {
39
+ network: repoCfg.network || globalCfg.network || exports.DEFAULT_NETWORK,
40
+ relays,
41
+ repo_id: repoCfg.repo_id,
42
+ };
43
+ }
44
+ function cleanRelays(relays) {
45
+ if (!relays || !relays.length)
46
+ return null;
47
+ const trimmed = relays.map((r) => r.trim()).filter(Boolean);
48
+ return trimmed.length ? trimmed : null;
49
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TESTNET_SUI_RPC = exports.WIT_MODULE_NAME = exports.WIT_PACKAGE_ID = void 0;
4
+ exports.WIT_PACKAGE_ID = '0x8c91d82b2292ac53a4fa5b21de86b1073230ac1d17dd6ae336ab5b559c329e09';
5
+ exports.WIT_MODULE_NAME = 'repository';
6
+ exports.TESTNET_SUI_RPC = 'https://fullnode.testnet.sui.io:443';
package/dist/lib/fs.js ADDED
@@ -0,0 +1,154 @@
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.pathToPosix = pathToPosix;
7
+ exports.modeToString = modeToString;
8
+ exports.mtimeSec = mtimeSec;
9
+ exports.computeFileMeta = computeFileMeta;
10
+ exports.readIndex = readIndex;
11
+ exports.writeIndex = writeIndex;
12
+ exports.blobFileName = blobFileName;
13
+ exports.ensureBlobFromFile = ensureBlobFromFile;
14
+ exports.readBlob = readBlob;
15
+ exports.ensureDirForFile = ensureDirForFile;
16
+ exports.removeFileIfExists = removeFileIfExists;
17
+ exports.buildIgnore = buildIgnore;
18
+ exports.shouldIgnore = shouldIgnore;
19
+ exports.walkFiles = walkFiles;
20
+ const crypto_1 = __importDefault(require("crypto"));
21
+ const promises_1 = __importDefault(require("fs/promises"));
22
+ const path_1 = __importDefault(require("path"));
23
+ const ignore_1 = __importDefault(require("ignore"));
24
+ const HASH_PREFIX = 'sha256-';
25
+ const DEFAULT_IGNORE_PATTERNS = ['.git', '.wit', 'node_modules'];
26
+ const BLOB_DIR = 'objects/blobs';
27
+ function pathToPosix(p) {
28
+ return p.split(path_1.default.sep).join('/');
29
+ }
30
+ function modeToString(mode) {
31
+ const isExec = (mode & 0o111) !== 0;
32
+ return `100${isExec ? '755' : '644'}`;
33
+ }
34
+ function mtimeSec(stat) {
35
+ return Math.floor(stat.mtimeMs / 1000);
36
+ }
37
+ async function computeFileMeta(filePath) {
38
+ const stat = await promises_1.default.stat(filePath);
39
+ const data = await promises_1.default.readFile(filePath);
40
+ const hash = HASH_PREFIX + crypto_1.default.createHash('sha256').update(data).digest('base64');
41
+ return {
42
+ hash,
43
+ size: stat.size,
44
+ mode: modeToString(stat.mode),
45
+ mtime: mtimeSec(stat),
46
+ };
47
+ }
48
+ async function readIndex(indexPath) {
49
+ try {
50
+ const raw = await promises_1.default.readFile(indexPath, 'utf8');
51
+ return JSON.parse(raw);
52
+ }
53
+ catch (err) {
54
+ if (err?.code === 'ENOENT')
55
+ return {};
56
+ throw err;
57
+ }
58
+ }
59
+ async function writeIndex(indexPath, index) {
60
+ const serialized = JSON.stringify(index, null, 2) + '\n';
61
+ await promises_1.default.writeFile(indexPath, serialized, 'utf8');
62
+ }
63
+ function blobFileName(hash) {
64
+ return hash.replace(/\//g, '_').replace(/\+/g, '-');
65
+ }
66
+ async function ensureBlobFromFile(witPath, hash, filePath) {
67
+ const blobPath = path_1.default.join(witPath, BLOB_DIR, blobFileName(hash));
68
+ try {
69
+ await promises_1.default.access(blobPath);
70
+ return;
71
+ }
72
+ catch (err) {
73
+ if (err?.code !== 'ENOENT')
74
+ throw err;
75
+ }
76
+ await promises_1.default.mkdir(path_1.default.dirname(blobPath), { recursive: true });
77
+ const data = await promises_1.default.readFile(filePath);
78
+ await promises_1.default.writeFile(blobPath, data);
79
+ }
80
+ async function readBlob(witPath, hash) {
81
+ const blobPath = path_1.default.join(witPath, BLOB_DIR, blobFileName(hash));
82
+ try {
83
+ return await promises_1.default.readFile(blobPath);
84
+ }
85
+ catch (err) {
86
+ if (err?.code === 'ENOENT')
87
+ return null;
88
+ throw err;
89
+ }
90
+ }
91
+ async function ensureDirForFile(filePath) {
92
+ await promises_1.default.mkdir(path_1.default.dirname(filePath), { recursive: true });
93
+ }
94
+ async function removeFileIfExists(filePath) {
95
+ try {
96
+ await promises_1.default.unlink(filePath);
97
+ }
98
+ catch (err) {
99
+ if (err?.code === 'ENOENT')
100
+ return;
101
+ throw err;
102
+ }
103
+ }
104
+ async function buildIgnore(root, extraPatterns = []) {
105
+ const ig = (0, ignore_1.default)();
106
+ ig.add(DEFAULT_IGNORE_PATTERNS);
107
+ ig.add(extraPatterns);
108
+ for (const file of ['.gitignore', '.witignore']) {
109
+ try {
110
+ const raw = await promises_1.default.readFile(path_1.default.join(root, file), 'utf8');
111
+ ig.add(raw);
112
+ }
113
+ catch (err) {
114
+ if (err?.code !== 'ENOENT')
115
+ throw err;
116
+ }
117
+ }
118
+ return ig;
119
+ }
120
+ function shouldIgnore(ig, relPosix, isDir) {
121
+ if (relPosix === '' || relPosix === '.')
122
+ return false;
123
+ const target = isDir ? (relPosix.endsWith('/') ? relPosix : `${relPosix}/`) : relPosix;
124
+ return ig.ignores(target);
125
+ }
126
+ async function walkFiles(root, ig, baseDir = root, tracked) {
127
+ const trackedList = tracked ? Array.from(tracked) : null;
128
+ async function walkDir(dir) {
129
+ const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
130
+ const files = [];
131
+ const dirTasks = [];
132
+ for (const entry of entries) {
133
+ const full = path_1.default.join(dir, entry.name);
134
+ const rel = path_1.default.relative(baseDir, full);
135
+ const relPosix = pathToPosix(rel);
136
+ const isDir = entry.isDirectory();
137
+ const hasTrackedDesc = trackedList && isDir ? trackedList.some((p) => p === relPosix || p.startsWith(`${relPosix}/`)) : false;
138
+ if (shouldIgnore(ig, relPosix, isDir) && !(tracked?.has(relPosix) || hasTrackedDesc))
139
+ continue;
140
+ if (isDir) {
141
+ dirTasks.push(walkDir(full));
142
+ }
143
+ else if (entry.isFile()) {
144
+ files.push(full);
145
+ }
146
+ }
147
+ if (dirTasks.length) {
148
+ const nested = await Promise.all(dirTasks);
149
+ nested.forEach((list) => files.push(...list));
150
+ }
151
+ return files;
152
+ }
153
+ return walkDir(root);
154
+ }