gitmark 0.0.76 → 0.0.77
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/.claude/settings.local.json +2 -1
- package/README.md +3 -0
- package/bin/git-mark.js +23 -2
- package/index.html +3 -0
- package/package.json +1 -1
- package/test/git-mark.test.js +107 -1
package/README.md
CHANGED
|
@@ -19,6 +19,9 @@ This gives you `git mark` as a native git subcommand.
|
|
|
19
19
|
# Initialize in a git repo (tbtc4 = Bitcoin testnet4)
|
|
20
20
|
git mark init --chain tbtc4 --voucher txo:tbtc4:txid:vout?amount=X&key=Y
|
|
21
21
|
|
|
22
|
+
# Or read vouchers from a file (uses last line, removes on success)
|
|
23
|
+
git mark init --voucher ~/faucet/vouchers.txt
|
|
24
|
+
|
|
22
25
|
# Make commits, then mark them
|
|
23
26
|
git commit -m "my change"
|
|
24
27
|
git mark
|
package/bin/git-mark.js
CHANGED
|
@@ -18,6 +18,7 @@ import { execSync } from 'child_process';
|
|
|
18
18
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
19
19
|
import { join, dirname } from 'path';
|
|
20
20
|
import { fileURLToPath } from 'url';
|
|
21
|
+
import { homedir } from 'os';
|
|
21
22
|
|
|
22
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
const PKG = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -197,6 +198,23 @@ function getPrivkey() {
|
|
|
197
198
|
function setPrivkey(key) { gitExec(`git config --local nostr.privkey '${key}'`); }
|
|
198
199
|
function isGitRoot() { return existsSync('.git'); }
|
|
199
200
|
|
|
201
|
+
function resolveVoucher(arg) {
|
|
202
|
+
let path = null;
|
|
203
|
+
if (arg.startsWith('file:')) path = arg.slice(5);
|
|
204
|
+
else if (arg.endsWith('.txt')) path = arg;
|
|
205
|
+
if (!path) return { uri: arg, file: null };
|
|
206
|
+
if (path.startsWith('~')) path = path.replace('~', homedir());
|
|
207
|
+
const lines = readFileSync(path, 'utf8').split('\n').map(l => l.trim()).filter(Boolean);
|
|
208
|
+
if (lines.length === 0) { console.error(`No vouchers in ${path}`); process.exit(1); }
|
|
209
|
+
return { uri: lines[lines.length - 1], file: path };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function consumeVoucher(path) {
|
|
213
|
+
const lines = readFileSync(path, 'utf8').split('\n').map(l => l.trim()).filter(Boolean);
|
|
214
|
+
lines.pop();
|
|
215
|
+
writeFileSync(path, lines.join('\n') + (lines.length ? '\n' : ''));
|
|
216
|
+
}
|
|
217
|
+
|
|
200
218
|
// --- Trail file helpers ---
|
|
201
219
|
function loadTrail() {
|
|
202
220
|
if (!existsSync(TRAIL_FILE)) return null;
|
|
@@ -306,7 +324,8 @@ async function cmdInit(args) {
|
|
|
306
324
|
// Voucher funding
|
|
307
325
|
const voucherIdx = args.indexOf('--voucher');
|
|
308
326
|
if (voucherIdx !== -1) {
|
|
309
|
-
const
|
|
327
|
+
const voucherArg = args[voucherIdx + 1];
|
|
328
|
+
const { uri: voucherUri, file: voucherFile } = resolveVoucher(voucherArg);
|
|
310
329
|
const txo = parseTxoUri(voucherUri);
|
|
311
330
|
if (!txo.key) { console.error('Voucher must include &key= parameter'); process.exit(1); }
|
|
312
331
|
if (!txo.amount) { console.error('Voucher must include &amount= parameter'); process.exit(1); }
|
|
@@ -334,6 +353,7 @@ async function cmdInit(args) {
|
|
|
334
353
|
);
|
|
335
354
|
const newTxid = await broadcastTx(rawTx, explorer);
|
|
336
355
|
|
|
356
|
+
if (voucherFile) consumeVoucher(voucherFile);
|
|
337
357
|
savePrivateState({ txid: newTxid, vout: 0, amount: outputAmount }, chain);
|
|
338
358
|
const xonly = pubkey.slice(2); // 64-char x-only Nostr pubkey
|
|
339
359
|
// Insert @id first
|
|
@@ -535,7 +555,8 @@ function cmdUpdate() {
|
|
|
535
555
|
export {
|
|
536
556
|
taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
|
|
537
557
|
pubkeyToAddress, parseTxoUri, p2trScript, buildTransaction,
|
|
538
|
-
TRAIL_FILE, PRIVATE_FILE, CHAINS, isDirty, loadTrailFromNotes, loadFullTrail
|
|
558
|
+
TRAIL_FILE, PRIVATE_FILE, CHAINS, isDirty, loadTrailFromNotes, loadFullTrail,
|
|
559
|
+
resolveVoucher, consumeVoucher
|
|
539
560
|
};
|
|
540
561
|
|
|
541
562
|
// --- CLI ---
|
package/index.html
CHANGED
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
<pre># Initialize in a git repo
|
|
69
69
|
git mark init --chain tbtc4 --voucher txo:tbtc4:txid:vout?amount=X&key=Y
|
|
70
70
|
|
|
71
|
+
# Or read vouchers from a file (last line used, removed on success)
|
|
72
|
+
git mark init --voucher ~/faucet/vouchers.txt
|
|
73
|
+
|
|
71
74
|
# Make commits, then mark them
|
|
72
75
|
git commit -m "my change"
|
|
73
76
|
git mark
|
package/package.json
CHANGED
package/test/git-mark.test.js
CHANGED
|
@@ -5,8 +5,12 @@ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
|
|
8
|
-
pubkeyToAddress, parseTxoUri, p2trScript, CHAINS, isDirty, loadFullTrail, loadTrailFromNotes
|
|
8
|
+
pubkeyToAddress, parseTxoUri, p2trScript, CHAINS, isDirty, loadFullTrail, loadTrailFromNotes,
|
|
9
|
+
resolveVoucher, consumeVoucher
|
|
9
10
|
} from '../bin/git-mark.js';
|
|
11
|
+
import { writeFileSync, readFileSync, mkdtempSync, rmSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { tmpdir } from 'os';
|
|
10
14
|
|
|
11
15
|
describe('Key chaining', () => {
|
|
12
16
|
const privkey = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
|
@@ -254,3 +258,105 @@ describe('TXO URI in git config format', () => {
|
|
|
254
258
|
assert.strictEqual(parsed.amount, state.amount);
|
|
255
259
|
});
|
|
256
260
|
});
|
|
261
|
+
|
|
262
|
+
describe('Voucher resolution', () => {
|
|
263
|
+
const uri1 = 'txo:tbtc4:aaa111:0?amount=15568&key=c9cb5a57062b61afc58e5e76d8e587117ba06d2c4e9891ee658896a828e758a6';
|
|
264
|
+
const uri2 = 'txo:tbtc4:bbb222:1?amount=15568&key=c9cb5a57062b61afc58e5e76d8e587117ba06d2c4e9891ee658896a828e758a6';
|
|
265
|
+
const uri3 = 'txo:tbtc4:ccc333:2?amount=15568&key=c9cb5a57062b61afc58e5e76d8e587117ba06d2c4e9891ee658896a828e758a6';
|
|
266
|
+
|
|
267
|
+
it('returns URI as-is when not a file', () => {
|
|
268
|
+
const r = resolveVoucher(uri1);
|
|
269
|
+
assert.strictEqual(r.uri, uri1);
|
|
270
|
+
assert.strictEqual(r.file, null);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('reads last line from .txt file', () => {
|
|
274
|
+
const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
|
|
275
|
+
try {
|
|
276
|
+
const path = join(dir, 'vouchers.txt');
|
|
277
|
+
writeFileSync(path, `${uri1}\n${uri2}\n${uri3}\n`);
|
|
278
|
+
const r = resolveVoucher(path);
|
|
279
|
+
assert.strictEqual(r.uri, uri3);
|
|
280
|
+
assert.strictEqual(r.file, path);
|
|
281
|
+
} finally {
|
|
282
|
+
rmSync(dir, { recursive: true, force: true });
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('reads last line when using file: prefix', () => {
|
|
287
|
+
const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
|
|
288
|
+
try {
|
|
289
|
+
const path = join(dir, 'vouchers'); // no .txt suffix
|
|
290
|
+
writeFileSync(path, `${uri1}\n${uri2}\n`);
|
|
291
|
+
const r = resolveVoucher(`file:${path}`);
|
|
292
|
+
assert.strictEqual(r.uri, uri2);
|
|
293
|
+
assert.strictEqual(r.file, path);
|
|
294
|
+
} finally {
|
|
295
|
+
rmSync(dir, { recursive: true, force: true });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('expands ~ to home directory', () => {
|
|
300
|
+
const fakeHome = mkdtempSync(join(tmpdir(), 'gitmark-home-'));
|
|
301
|
+
const originalHome = process.env.HOME;
|
|
302
|
+
process.env.HOME = fakeHome;
|
|
303
|
+
try {
|
|
304
|
+
const path = join(fakeHome, 'vouchers.txt');
|
|
305
|
+
writeFileSync(path, `${uri1}\n`);
|
|
306
|
+
const r = resolveVoucher('~/vouchers.txt');
|
|
307
|
+
assert.strictEqual(r.uri, uri1);
|
|
308
|
+
assert.strictEqual(r.file, path);
|
|
309
|
+
} finally {
|
|
310
|
+
process.env.HOME = originalHome;
|
|
311
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('exits with error on empty file', () => {
|
|
316
|
+
const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
|
|
317
|
+
const originalExit = process.exit;
|
|
318
|
+
const originalError = console.error;
|
|
319
|
+
let exitCode = null;
|
|
320
|
+
let errorMsg = null;
|
|
321
|
+
process.exit = (code) => { exitCode = code; throw new Error('exit called'); };
|
|
322
|
+
console.error = (msg) => { errorMsg = msg; };
|
|
323
|
+
try {
|
|
324
|
+
const path = join(dir, 'empty.txt');
|
|
325
|
+
writeFileSync(path, '');
|
|
326
|
+
assert.throws(() => resolveVoucher(path), /exit called/);
|
|
327
|
+
assert.strictEqual(exitCode, 1);
|
|
328
|
+
assert.match(errorMsg, /No vouchers/);
|
|
329
|
+
} finally {
|
|
330
|
+
process.exit = originalExit;
|
|
331
|
+
console.error = originalError;
|
|
332
|
+
rmSync(dir, { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('consumeVoucher removes last line', () => {
|
|
337
|
+
const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
|
|
338
|
+
try {
|
|
339
|
+
const path = join(dir, 'vouchers.txt');
|
|
340
|
+
writeFileSync(path, `${uri1}\n${uri2}\n${uri3}\n`);
|
|
341
|
+
consumeVoucher(path);
|
|
342
|
+
const remaining = readFileSync(path, 'utf8').trim().split('\n');
|
|
343
|
+
assert.strictEqual(remaining.length, 2);
|
|
344
|
+
assert.strictEqual(remaining[0], uri1);
|
|
345
|
+
assert.strictEqual(remaining[1], uri2);
|
|
346
|
+
} finally {
|
|
347
|
+
rmSync(dir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('consumeVoucher handles last remaining line', () => {
|
|
352
|
+
const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
|
|
353
|
+
try {
|
|
354
|
+
const path = join(dir, 'vouchers.txt');
|
|
355
|
+
writeFileSync(path, `${uri1}\n`);
|
|
356
|
+
consumeVoucher(path);
|
|
357
|
+
assert.strictEqual(readFileSync(path, 'utf8'), '');
|
|
358
|
+
} finally {
|
|
359
|
+
rmSync(dir, { recursive: true, force: true });
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
});
|