@dk/hipp 0.1.8 → 0.1.13
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 +27 -4
- package/hipp.js +69 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,20 +148,43 @@ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
148
148
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
149
149
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
150
150
|
PERFORMANCE OF THIS SOFTWARE.
|
|
151
|
+
|
|
152
|
+
|
|
151
153
|
```json
|
|
152
154
|
{
|
|
153
155
|
"origin": "git@github.com:dmytri/hipp.git",
|
|
154
|
-
"tag": "v0.1.
|
|
156
|
+
"tag": "v0.1.10"
|
|
155
157
|
}
|
|
156
158
|
```
|
|
157
159
|
|
|
158
|
-
```npx @dk/hipp @dk/hipp@0.1.
|
|
160
|
+
```npx @dk/hipp @dk/hipp@0.1.10
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
<!-- HIPP-META -->
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"origin": "git@github.com:dmytri/hipp.git",
|
|
167
|
+
"tag": "v0.1.11"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```npx @dk/hipp @dk/hipp@0.1.11
|
|
172
|
+
```
|
|
173
|
+
<!-- /HIPP-META -->
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"origin": "git@github.com:dmytri/hipp.git",
|
|
178
|
+
"tag": "v0.1.13"
|
|
179
|
+
}
|
|
180
|
+
```
|
|
159
181
|
|
|
182
|
+
```npx @dk/hipp @dk/hipp@0.1.13```
|
|
160
183
|
<!-- HIPP-MANIFEST -->
|
|
161
184
|
```json
|
|
162
185
|
{
|
|
163
|
-
"hash": "
|
|
164
|
-
"signature": "
|
|
186
|
+
"hash": "0157b775d49e9715e954bc9838f5017632c0fd2e2418d814b0e2bbb92abe3fc3",
|
|
187
|
+
"signature": "jyq8xJjFAt5HtqQWPPHw52A2ph7zVxEYD1QClEY3jRFcVFdUlaJj+sGatgP/FChjaI57HZM8flYUvl2r1DA5Dw=="
|
|
165
188
|
}
|
|
166
189
|
```
|
|
167
190
|
<!-- /HIPP-MANIFEST -->
|
package/hipp.js
CHANGED
|
@@ -167,6 +167,7 @@ function extractJsonMetaFromReadme(readmeContent) {
|
|
|
167
167
|
let jsonStart = -1;
|
|
168
168
|
let braceCount = 0;
|
|
169
169
|
let inJson = false;
|
|
170
|
+
let lastValid = null;
|
|
170
171
|
|
|
171
172
|
for (let i = 0; i < lines.length; i++) {
|
|
172
173
|
const line = lines[i];
|
|
@@ -186,7 +187,7 @@ function extractJsonMetaFromReadme(readmeContent) {
|
|
|
186
187
|
try {
|
|
187
188
|
const parsed = JSON.parse(jsonStr);
|
|
188
189
|
if (parsed.origin && parsed.tag) {
|
|
189
|
-
|
|
190
|
+
lastValid = parsed;
|
|
190
191
|
}
|
|
191
192
|
} catch {
|
|
192
193
|
// continue searching
|
|
@@ -195,7 +196,7 @@ function extractJsonMetaFromReadme(readmeContent) {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
|
-
return
|
|
199
|
+
return lastValid;
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
function safeStageName(name) {
|
|
@@ -372,6 +373,19 @@ function getTrackedFiles() {
|
|
|
372
373
|
.filter(Boolean);
|
|
373
374
|
}
|
|
374
375
|
|
|
376
|
+
function getTrackedFilesFromDir(repoDir) {
|
|
377
|
+
const out = execFileSync('git', ['ls-files', '-z'], {
|
|
378
|
+
encoding: 'buffer',
|
|
379
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
380
|
+
cwd: repoDir,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return out
|
|
384
|
+
.toString('utf8')
|
|
385
|
+
.split('\0')
|
|
386
|
+
.filter(Boolean);
|
|
387
|
+
}
|
|
388
|
+
|
|
375
389
|
function copyTrackedFiles(stageDir, files) {
|
|
376
390
|
const repoRoot = process.cwd();
|
|
377
391
|
|
|
@@ -393,6 +407,25 @@ function copyTrackedFiles(stageDir, files) {
|
|
|
393
407
|
}
|
|
394
408
|
}
|
|
395
409
|
|
|
410
|
+
function copyTrackedFilesFromDir(stageDir, repoDir, files) {
|
|
411
|
+
for (const rel of files) {
|
|
412
|
+
const src = path.join(repoDir, rel);
|
|
413
|
+
const dest = path.join(stageDir, rel);
|
|
414
|
+
const stat = fs.lstatSync(src);
|
|
415
|
+
|
|
416
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
417
|
+
|
|
418
|
+
if (stat.isSymbolicLink()) {
|
|
419
|
+
const target = fs.readlinkSync(src);
|
|
420
|
+
fs.symlinkSync(target, dest);
|
|
421
|
+
} else if (stat.isDirectory()) {
|
|
422
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
423
|
+
} else if (stat.isFile()) {
|
|
424
|
+
fs.copyFileSync(src, dest);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
396
429
|
async function runVerify(packageSpec) {
|
|
397
430
|
let pkgName, pkgVersion;
|
|
398
431
|
if (packageSpec.startsWith('@')) {
|
|
@@ -462,45 +495,55 @@ async function runVerify(packageSpec) {
|
|
|
462
495
|
fail(`❌ JSON meta (origin/tag) not found in README`);
|
|
463
496
|
}
|
|
464
497
|
|
|
465
|
-
const
|
|
466
|
-
if (!
|
|
498
|
+
const manifestStr = extractManifestFromReadme(stagedReadme);
|
|
499
|
+
if (!manifestStr) {
|
|
467
500
|
fail(`❌ Manifest not found in README`);
|
|
468
501
|
}
|
|
469
502
|
|
|
470
|
-
const manifest = parseManifest(
|
|
503
|
+
const manifest = parseManifest(manifestStr);
|
|
471
504
|
if (!manifest || !manifest.hash || !manifest.signature) {
|
|
472
505
|
fail(`❌ Invalid manifest format`);
|
|
473
506
|
}
|
|
474
507
|
|
|
508
|
+
const originUrl = jsonMeta.origin;
|
|
509
|
+
const tag = jsonMeta.tag;
|
|
510
|
+
|
|
475
511
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
|
|
512
|
+
const stageDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-stage-`));
|
|
513
|
+
|
|
476
514
|
try {
|
|
477
|
-
log.info(`🌿 Fetching from git origin...`);
|
|
515
|
+
log.info(`🌿 Fetching from git origin at tag ${tag}...`);
|
|
516
|
+
git(['clone', '--branch', tag, '--depth', '1', originUrl, tmpDir], { stdio: 'pipe' });
|
|
478
517
|
|
|
479
|
-
const
|
|
480
|
-
|
|
518
|
+
const publicKeyPath = path.join(tmpDir, 'hipp.pub');
|
|
519
|
+
if (!fs.existsSync(publicKeyPath)) {
|
|
520
|
+
fail(`❌ hipp.pub not found in git at tag ${tag}`);
|
|
521
|
+
}
|
|
481
522
|
|
|
482
|
-
|
|
523
|
+
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
|
|
524
|
+
|
|
525
|
+
log.info(`🏗️ Staging git files...`);
|
|
526
|
+
const trackedFiles = getTrackedFilesFromDir(tmpDir);
|
|
527
|
+
copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
|
|
483
528
|
|
|
484
|
-
const
|
|
485
|
-
if (!fs.existsSync(
|
|
529
|
+
const stagedReadmePath = path.join(stageDir, 'README.md');
|
|
530
|
+
if (!fs.existsSync(stagedReadmePath)) {
|
|
486
531
|
fail(`❌ README.md not found in git at tag ${tag}`);
|
|
487
532
|
}
|
|
488
533
|
|
|
489
|
-
|
|
490
|
-
const
|
|
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);
|
|
491
540
|
|
|
492
|
-
if (
|
|
541
|
+
if (stagedHash !== manifest.hash) {
|
|
493
542
|
fail(`❌ Hash mismatch: git content does not match npm manifest`);
|
|
494
543
|
}
|
|
495
544
|
|
|
496
545
|
log.success(`🔒 Content hash verified: ${manifest.hash.slice(0, 12)}...`);
|
|
497
546
|
|
|
498
|
-
const publicKeyPath = path.join(tmpDir, 'hipp.pub');
|
|
499
|
-
if (!fs.existsSync(publicKeyPath)) {
|
|
500
|
-
fail(`❌ hipp.pub not found in git at tag ${tag}`);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
|
|
504
547
|
const signData = buildSignData(manifest.hash, originUrl, tag);
|
|
505
548
|
const signatureValid = verifySignature(signData, manifest.signature, publicKey);
|
|
506
549
|
|
|
@@ -514,6 +557,7 @@ async function runVerify(packageSpec) {
|
|
|
514
557
|
log.info(`📍 Tag: ${tag}`);
|
|
515
558
|
} finally {
|
|
516
559
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
560
|
+
fs.rmSync(stageDir, { recursive: true, force: true });
|
|
517
561
|
}
|
|
518
562
|
} finally {
|
|
519
563
|
fs.rmSync(tarballPath, { recursive: true, force: true });
|
|
@@ -565,9 +609,10 @@ async function run() {
|
|
|
565
609
|
log.success('📝 hipp.pub committed.');
|
|
566
610
|
}
|
|
567
611
|
|
|
612
|
+
const { rawTag, version } = getVersionFromExactTagOnHead();
|
|
613
|
+
|
|
568
614
|
ensureCleanRepo(pkg);
|
|
569
615
|
|
|
570
|
-
const { rawTag, version } = getVersionFromExactTagOnHead();
|
|
571
616
|
const refInfo = ensureMutableRefPolicy();
|
|
572
617
|
const provenance = ensureRemoteProvenance(rawTag, refInfo.head);
|
|
573
618
|
const lockInfo = ensureLockIntegrity(pkg);
|
|
@@ -608,7 +653,9 @@ async function run() {
|
|
|
608
653
|
stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
|
|
609
654
|
}
|
|
610
655
|
|
|
611
|
-
|
|
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';
|
|
612
659
|
|
|
613
660
|
const readmeHash = computeReadmeHash(stagedReadme);
|
|
614
661
|
const dataToSign = buildSignData(readmeHash, provenance.remoteUrl, rawTag);
|