@dk/hipp 0.1.13 → 0.1.15

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.
Files changed (3) hide show
  1. package/README.md +3 -11
  2. package/hipp.js +49 -83
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -175,16 +175,8 @@ PERFORMANCE OF THIS SOFTWARE.
175
175
  ```json
176
176
  {
177
177
  "origin": "git@github.com:dmytri/hipp.git",
178
- "tag": "v0.1.13"
178
+ "tag": "v0.1.15",
179
+ "hash": "cee8b863e598809fe8a194c1f102c857a0698e8158bef9e39493b31da6411226",
180
+ "signature": "Hc5fpg5Kg67ztJBdbtM3DvdrccL1e0KEyYwenwVIHLqcCNadXT9oatpgkO9dFaaGkCMUfttMGtWv8iJtYy+tBg=="
179
181
  }
180
182
  ```
181
-
182
- ```npx @dk/hipp @dk/hipp@0.1.13```
183
- <!-- HIPP-MANIFEST -->
184
- ```json
185
- {
186
- "hash": "0157b775d49e9715e954bc9838f5017632c0fd2e2418d814b0e2bbb92abe3fc3",
187
- "signature": "jyq8xJjFAt5HtqQWPPHw52A2ph7zVxEYD1QClEY3jRFcVFdUlaJj+sGatgP/FChjaI57HZM8flYUvl2r1DA5Dw=="
188
- }
189
- ```
190
- <!-- /HIPP-MANIFEST -->
package/hipp.js CHANGED
@@ -123,46 +123,7 @@ function buildSignData(hash, origin, tag) {
123
123
  return `${hash}\n${origin}\n${tag}\n`;
124
124
  }
125
125
 
126
- const MANIFEST_START = '<!-- HIPP-MANIFEST -->';
127
- const MANIFEST_END = '<!-- /HIPP-MANIFEST -->';
128
-
129
- function appendManifestToReadme(readmeContent, manifest) {
130
- return `${readmeContent}${MANIFEST_START}\n\`\`\`json\n${manifest}\n\`\`\`\n${MANIFEST_END}\n`;
131
- }
132
-
133
- function extractManifestFromReadme(readmeContent) {
134
- const startIdx = readmeContent.indexOf(MANIFEST_START);
135
- if (startIdx === -1) return null;
136
- const endIdx = readmeContent.indexOf(MANIFEST_END, startIdx);
137
- if (endIdx === -1) return null;
138
- const content = readmeContent.slice(startIdx + MANIFEST_START.length, endIdx).trim();
139
- const match = content.match(/^```json\n(.+)\n```$/s);
140
- if (!match) return null;
141
- return match[1];
142
- }
143
-
144
- function stripManifestFromReadme(readmeContent) {
145
- const startIdx = readmeContent.indexOf(MANIFEST_START);
146
- const endIdx = readmeContent.indexOf(MANIFEST_END, startIdx);
147
- if (startIdx === -1 || endIdx === -1) return readmeContent;
148
-
149
- const endLineIdx = readmeContent.indexOf('\n', endIdx);
150
- const endOfManifest = endLineIdx !== -1 ? endLineIdx + 1 : readmeContent.length;
151
-
152
- const beforeWithNewline = readmeContent.slice(0, startIdx);
153
- const lastNewlineBefore = beforeWithNewline.lastIndexOf('\n');
154
- const before = lastNewlineBefore !== -1 ? beforeWithNewline.slice(0, lastNewlineBefore) : beforeWithNewline;
155
-
156
- const after = readmeContent.slice(endOfManifest);
157
- return before + '\n' + after;
158
- }
159
-
160
- function computeReadmeHash(readmeContent) {
161
- const stripped = stripManifestFromReadme(readmeContent);
162
- return sha256(stripped);
163
- }
164
-
165
- function extractJsonMetaFromReadme(readmeContent) {
126
+ function findLastJsonBlock(readmeContent) {
166
127
  const lines = readmeContent.split('\n');
167
128
  let jsonStart = -1;
168
129
  let braceCount = 0;
@@ -186,7 +147,7 @@ function extractJsonMetaFromReadme(readmeContent) {
186
147
  const jsonStr = lines.slice(jsonStart, i + 1).join('\n');
187
148
  try {
188
149
  const parsed = JSON.parse(jsonStr);
189
- if (parsed.origin && parsed.tag) {
150
+ if (parsed.origin && parsed.tag && parsed.hash && parsed.signature) {
190
151
  lastValid = parsed;
191
152
  }
192
153
  } catch {
@@ -199,6 +160,26 @@ function extractJsonMetaFromReadme(readmeContent) {
199
160
  return lastValid;
200
161
  }
201
162
 
163
+ function packAndHash(stageDir) {
164
+ const result = spawnSync('npm', ['pack'], {
165
+ cwd: stageDir,
166
+ encoding: 'utf8',
167
+ stdio: ['pipe', 'pipe', 'pipe'],
168
+ });
169
+
170
+ if (result.status !== 0) {
171
+ throw new Error(`npm pack failed: ${result.stderr}`);
172
+ }
173
+
174
+ const tarballName = result.stdout.trim().split('\n').pop();
175
+ const tarballPath = path.join(stageDir, tarballName);
176
+
177
+ const tarballContent = fs.readFileSync(tarballPath);
178
+ fs.unlinkSync(tarballPath);
179
+
180
+ return { tarballName, tarballHash: sha256(tarballContent) };
181
+ }
182
+
202
183
  function safeStageName(name) {
203
184
  return name.replace(/[^a-zA-Z0-9._-]/g, '-');
204
185
  }
@@ -490,23 +471,12 @@ async function runVerify(packageSpec) {
490
471
  }
491
472
 
492
473
  const stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
493
- const jsonMeta = extractJsonMetaFromReadme(stagedReadme);
494
- if (!jsonMeta || !jsonMeta.origin || !jsonMeta.tag) {
495
- fail(`❌ JSON meta (origin/tag) not found in README`);
496
- }
497
-
498
- const manifestStr = extractManifestFromReadme(stagedReadme);
499
- if (!manifestStr) {
500
- fail(`❌ Manifest not found in README`);
501
- }
502
-
503
- const manifest = parseManifest(manifestStr);
504
- if (!manifest || !manifest.hash || !manifest.signature) {
505
- fail(`❌ Invalid manifest format`);
474
+ const manifest = findLastJsonBlock(stagedReadme);
475
+ if (!manifest || !manifest.origin || !manifest.tag || !manifest.hash || !manifest.signature) {
476
+ fail(`❌ Manifest not found or invalid in README`);
506
477
  }
507
478
 
508
- const originUrl = jsonMeta.origin;
509
- const tag = jsonMeta.tag;
479
+ const { origin: originUrl, tag, hash: npmHash, signature } = manifest;
510
480
 
511
481
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
512
482
  const stageDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-stage-`));
@@ -526,26 +496,17 @@ async function runVerify(packageSpec) {
526
496
  const trackedFiles = getTrackedFilesFromDir(tmpDir);
527
497
  copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
528
498
 
529
- const stagedReadmePath = path.join(stageDir, 'README.md');
530
- if (!fs.existsSync(stagedReadmePath)) {
531
- fail(`❌ README.md not found in git at tag ${tag}`);
532
- }
533
-
534
- let stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
535
- const verifyBlock = '```npx @dk/hipp ' + pkgName + '@' + tag + '```';
536
- const jsonMetaStr = '```json\n{\n "origin": "' + originUrl + '",\n "tag": "' + tag + '"\n}\n```';
537
- stagedReadme = stagedReadme.trimEnd() + '\n\n' + jsonMetaStr + '\n\n' + verifyBlock + '\n';
538
-
539
- const stagedHash = computeReadmeHash(stagedReadme);
499
+ log.info(`📦 Packing to verify content hash...`);
500
+ const { tarballHash } = packAndHash(stageDir);
540
501
 
541
- if (stagedHash !== manifest.hash) {
502
+ if (tarballHash !== npmHash) {
542
503
  fail(`❌ Hash mismatch: git content does not match npm manifest`);
543
504
  }
544
505
 
545
- log.success(`🔒 Content hash verified: ${manifest.hash.slice(0, 12)}...`);
506
+ log.success(`🔒 Content hash verified: ${npmHash.slice(0, 12)}...`);
546
507
 
547
- const signData = buildSignData(manifest.hash, originUrl, tag);
548
- const signatureValid = verifySignature(signData, manifest.signature, publicKey);
508
+ const signData = buildSignData(npmHash, originUrl, tag);
509
+ const signatureValid = verifySignature(signData, signature, publicKey);
549
510
 
550
511
  if (!signatureValid) {
551
512
  fail(`❌ Signature verification failed`);
@@ -642,10 +603,9 @@ async function run() {
642
603
 
643
604
  const { privateKey } = loadOrGenerateKeys();
644
605
 
645
- const stagedPkgPath = path.join(stageDir, 'package.json');
646
- const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
647
- stagedPkg.version = version;
648
- fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
606
+ log.info(`📦 Packing to compute content hash...`);
607
+ const { tarballHash } = packAndHash(stageDir);
608
+ log.success(`🔒 Content hash: ${tarballHash.slice(0, 12)}...`);
649
609
 
650
610
  const stagedReadmePath = path.join(stageDir, 'README.md');
651
611
  let stagedReadme = '';
@@ -653,18 +613,24 @@ async function run() {
653
613
  stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
654
614
  }
655
615
 
656
- const verifyBlock = '```npx @dk/hipp ' + pkg.name + '@' + version + '```';
657
- const jsonMetaStr = '```json\n{\n "origin": "' + provenance.remoteUrl + '",\n "tag": "' + rawTag + '"\n}\n```';
658
- stagedReadme = stagedReadme.trimEnd() + '\n\n' + jsonMetaStr + '\n\n' + verifyBlock + '\n';
659
-
660
- const readmeHash = computeReadmeHash(stagedReadme);
661
- const dataToSign = buildSignData(readmeHash, provenance.remoteUrl, rawTag);
616
+ const dataToSign = buildSignData(tarballHash, provenance.remoteUrl, rawTag);
662
617
  const signature = signContent(dataToSign, privateKey);
663
- const manifest = createManifest(readmeHash, signature);
664
- stagedReadme = appendManifestToReadme(stagedReadme, manifest);
665
618
 
619
+ const manifestJson = {
620
+ origin: provenance.remoteUrl,
621
+ tag: rawTag,
622
+ hash: tarballHash,
623
+ signature: signature,
624
+ };
625
+
626
+ stagedReadme = stagedReadme.trimEnd() + '\n\n```json\n' + JSON.stringify(manifestJson, null, 2) + '\n```\n';
666
627
  fs.writeFileSync(stagedReadmePath, stagedReadme);
667
628
 
629
+ const stagedPkgPath = path.join(stageDir, 'package.json');
630
+ const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
631
+ stagedPkg.version = version;
632
+ fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
633
+
668
634
  log.success('🔏 Manifest signed.');
669
635
 
670
636
  log.info('🔥 Ignition...');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dk/hipp",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "High Integrity Package Publisher",
5
5
  "main": "hipp.js",
6
6
  "bin": {