gitmark 0.0.78 → 0.0.79
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 +9 -0
- package/bin/git-mark.js +49 -1
- package/package.json +1 -1
- package/test/git-mark.test.js +36 -1
package/README.md
CHANGED
|
@@ -68,8 +68,17 @@ The chain of addresses on Bitcoin mirrors the chain of commits in git. Anyone ca
|
|
|
68
68
|
| `git mark info` | Show trail state, balance, addresses |
|
|
69
69
|
| `git mark verify` | Verify all marks against Bitcoin |
|
|
70
70
|
| `git mark update` | Update blocktrails.json from git notes |
|
|
71
|
+
| `git mark badge` | Print README badge markdown. Option: `--branch` |
|
|
71
72
|
| `git mark --version` | Show version |
|
|
72
73
|
|
|
74
|
+
## Badge
|
|
75
|
+
|
|
76
|
+
`git mark badge` prints markdown for a README badge, built from the `origin` remote, the current branch (or `--branch`) and `blocktrails.json`. For example, this is the badge for [melvincarvalho/delivery-day](https://github.com/melvincarvalho/delivery-day), whose trail is on txbt4:
|
|
77
|
+
|
|
78
|
+
[](https://mempool.guide/testnet4/tx/8129190799de150153bdae7d1092c4cd505874e842cfbf429ac452956025fa51)
|
|
79
|
+
|
|
80
|
+
It's a shields.io [dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) that counts `states` in the pushed trail file, and links to the latest mark on the chain's explorer. The count updates whenever `blocktrails.json` is pushed, so it shows what the repo records. `git mark verify` checks it against the chain. For remotes not on GitHub, it prints `RAW_TRAIL_URL` and `TRAIL_LINK` placeholders to fill in.
|
|
81
|
+
|
|
73
82
|
## Trail File
|
|
74
83
|
|
|
75
84
|
`blocktrails.json` in your repo root — committed, visible, verifiable:
|
package/bin/git-mark.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* git mark info
|
|
10
10
|
* git mark verify
|
|
11
11
|
* git mark update
|
|
12
|
+
* git mark badge [--branch name]
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
|
@@ -565,6 +566,48 @@ async function cmdVerify() {
|
|
|
565
566
|
process.exit(ok ? 0 : 1);
|
|
566
567
|
}
|
|
567
568
|
|
|
569
|
+
// --- Badge ---
|
|
570
|
+
// owner/repo from a GitHub remote URL (https, ssh or scp form), or null
|
|
571
|
+
function parseGithubRemote(url) {
|
|
572
|
+
const m = String(url || '').trim().match(/^(?:https?:\/\/|ssh:\/\/git@|git@)github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
573
|
+
return m ? { owner: m[1], repo: m[2] } : null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// the explorer's web page for a transaction: its API base without /api
|
|
577
|
+
function explorerTxUrl(chain, txid) {
|
|
578
|
+
const api = CHAINS[chain]?.explorer;
|
|
579
|
+
return api ? `${api.replace(/\/api$/, '')}/tx/${txid}` : null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Markdown for a shields.io dynamic JSON badge counting the marks in the pushed trail file.
|
|
583
|
+
// trailUrl: where the raw blocktrails.json is served; link: where the badge points.
|
|
584
|
+
function badgeMarkdown({ trailUrl, chain, link }) {
|
|
585
|
+
const q = new URLSearchParams({ url: trailUrl, query: '$.states.length', label: 'gitmarks', suffix: ` · ${chain}`, color: 'f7931a' });
|
|
586
|
+
const img = `https://img.shields.io/badge/dynamic/json?${q.toString().replace(/\+/g, '%20')}`;
|
|
587
|
+
return `[](${link})`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function cmdBadge(args) {
|
|
591
|
+
const trail = loadFullTrail();
|
|
592
|
+
if (!trail) { console.error(`No ${TRAIL_FILE} found. Run: git mark init`); process.exit(1); }
|
|
593
|
+
const branchIdx = args.indexOf('--branch');
|
|
594
|
+
const quiet = (cmd) => { try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); } catch { return ''; } };
|
|
595
|
+
const branch = (branchIdx !== -1 ? args[branchIdx + 1] : null) || quiet('git symbolic-ref --short HEAD') || 'main';
|
|
596
|
+
const remote = quiet('git remote get-url origin');
|
|
597
|
+
const gh = parseGithubRemote(remote);
|
|
598
|
+
|
|
599
|
+
// the latest mark; before the first one, the badge links to the trail file instead
|
|
600
|
+
const lastTxid = trail.txo.length ? parseTxoUri(trail.txo[trail.txo.length - 1]).txid : null;
|
|
601
|
+
const trailUrl = gh
|
|
602
|
+
? `https://raw.githubusercontent.com/${gh.owner}/${gh.repo}/${branch}/${TRAIL_FILE}`
|
|
603
|
+
: 'RAW_TRAIL_URL';
|
|
604
|
+
const link = (lastTxid && explorerTxUrl(trail.chain, lastTxid))
|
|
605
|
+
|| (gh ? `https://github.com/${gh.owner}/${gh.repo}/blob/${branch}/${TRAIL_FILE}` : 'TRAIL_LINK');
|
|
606
|
+
|
|
607
|
+
console.log(badgeMarkdown({ trailUrl, chain: trail.chain, link }));
|
|
608
|
+
if (!gh) console.error(`\nOrigin is not on GitHub: replace RAW_TRAIL_URL with the URL-encoded address the pushed ${TRAIL_FILE} is served from, and TRAIL_LINK with where the badge should point.`);
|
|
609
|
+
}
|
|
610
|
+
|
|
568
611
|
function cmdUpdate() {
|
|
569
612
|
const trail = loadTrailFromNotes();
|
|
570
613
|
if (!trail) { console.error(`No ${TRAIL_FILE} found. Run: git mark init`); process.exit(1); }
|
|
@@ -577,7 +620,8 @@ export {
|
|
|
577
620
|
taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
|
|
578
621
|
pubkeyToAddress, parseTxoUri, p2trScript, buildTransaction,
|
|
579
622
|
TRAIL_FILE, PRIVATE_FILE, CHAINS, isDirty, loadTrailFromNotes, loadFullTrail,
|
|
580
|
-
resolveVoucher, consumeVoucher
|
|
623
|
+
resolveVoucher, consumeVoucher,
|
|
624
|
+
parseGithubRemote, explorerTxUrl, badgeMarkdown
|
|
581
625
|
};
|
|
582
626
|
|
|
583
627
|
// --- CLI ---
|
|
@@ -596,6 +640,8 @@ if (isMain) {
|
|
|
596
640
|
cmdVerify();
|
|
597
641
|
} else if (cmd === 'update') {
|
|
598
642
|
cmdUpdate();
|
|
643
|
+
} else if (cmd === 'badge') {
|
|
644
|
+
cmdBadge(args.slice(1));
|
|
599
645
|
} else if (cmd === 'mark' || !cmd || (cmd && !cmd.startsWith('-'))) {
|
|
600
646
|
if (!existsSync(TRAIL_FILE) && cmd !== 'mark') {
|
|
601
647
|
console.log('Usage:');
|
|
@@ -604,6 +650,7 @@ if (isMain) {
|
|
|
604
650
|
console.log(' git mark info # show trail state');
|
|
605
651
|
console.log(' git mark verify # verify trail against Bitcoin');
|
|
606
652
|
console.log(' git mark update # update blocktrails.json from git notes');
|
|
653
|
+
console.log(' git mark badge # print README badge markdown');
|
|
607
654
|
} else {
|
|
608
655
|
cmdMark(args.slice(cmd === 'mark' ? 1 : 0));
|
|
609
656
|
}
|
|
@@ -613,5 +660,6 @@ if (isMain) {
|
|
|
613
660
|
console.log(' git mark # anchor HEAD to Bitcoin');
|
|
614
661
|
console.log(' git mark info # show trail state');
|
|
615
662
|
console.log(' git mark verify # verify trail against Bitcoin');
|
|
663
|
+
console.log(' git mark badge # print README badge markdown');
|
|
616
664
|
}
|
|
617
665
|
}
|
package/package.json
CHANGED
package/test/git-mark.test.js
CHANGED
|
@@ -6,7 +6,8 @@ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
|
6
6
|
import {
|
|
7
7
|
taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
|
|
8
8
|
pubkeyToAddress, parseTxoUri, p2trScript, CHAINS, isDirty, loadFullTrail, loadTrailFromNotes,
|
|
9
|
-
resolveVoucher, consumeVoucher, buildTransaction
|
|
9
|
+
resolveVoucher, consumeVoucher, buildTransaction,
|
|
10
|
+
parseGithubRemote, explorerTxUrl, badgeMarkdown
|
|
10
11
|
} from '../bin/git-mark.js';
|
|
11
12
|
import { unifiedSighash, parseTransaction, SCRIPT_TYPE_TAPROOT } from '../lib/unified-sighash.js';
|
|
12
13
|
import { schnorr } from '@noble/curves/secp256k1';
|
|
@@ -438,3 +439,37 @@ describe('Signing', () => {
|
|
|
438
439
|
assert.notDeepStrictEqual(plain.inputs[0].witness[0], unified.inputs[0].witness[0].slice(0, 64));
|
|
439
440
|
});
|
|
440
441
|
});
|
|
442
|
+
|
|
443
|
+
describe('Badge', () => {
|
|
444
|
+
it('reads owner and repo from GitHub remotes in every form', () => {
|
|
445
|
+
for (const url of [
|
|
446
|
+
'https://github.com/melvincarvalho/delivery-day.git',
|
|
447
|
+
'https://github.com/melvincarvalho/delivery-day',
|
|
448
|
+
'git@github.com:melvincarvalho/delivery-day.git',
|
|
449
|
+
'ssh://git@github.com/melvincarvalho/delivery-day.git',
|
|
450
|
+
]) assert.deepStrictEqual(parseGithubRemote(url), { owner: 'melvincarvalho', repo: 'delivery-day' }, url);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('returns null for remotes not on GitHub', () => {
|
|
454
|
+
assert.strictEqual(parseGithubRemote('https://gitlab.com/a/b.git'), null);
|
|
455
|
+
assert.strictEqual(parseGithubRemote(''), null);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('links a transaction on the chain explorer', () => {
|
|
459
|
+
assert.strictEqual(explorerTxUrl('txbt4', 'ab'), 'https://mempool.guide/testnet4/tx/ab');
|
|
460
|
+
assert.strictEqual(explorerTxUrl('xbt', 'ab'), 'https://mempool.kilombino.com/tx/ab');
|
|
461
|
+
assert.strictEqual(explorerTxUrl('btc', 'ab'), 'https://mempool.space/tx/ab');
|
|
462
|
+
assert.strictEqual(explorerTxUrl('nope', 'ab'), null);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('builds a shields.io dynamic JSON badge counting states', () => {
|
|
466
|
+
const md = badgeMarkdown({
|
|
467
|
+
trailUrl: 'https://raw.githubusercontent.com/melvincarvalho/delivery-day/gh-pages/blocktrails.json',
|
|
468
|
+
chain: 'txbt4',
|
|
469
|
+
link: 'https://mempool.guide/testnet4/tx/ab',
|
|
470
|
+
});
|
|
471
|
+
assert.strictEqual(md,
|
|
472
|
+
'[](https://mempool.guide/testnet4/tx/ab)');
|
|
474
|
+
});
|
|
475
|
+
});
|