@dk/hipp 0.1.6 → 0.1.8

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 (4) hide show
  1. package/README.md +104 -24
  2. package/hipp.js +327 -1
  3. package/hipp.pub +3 -0
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🚀 HIPP: High Integrity Package Publisher
1
+ # HIPP: High Integrity Package Publisher
2
2
 
3
3
  By Dmytri Kleiner <dev@dmytri.to>
4
4
 
@@ -7,43 +7,51 @@ friction of version-bump commits. It treats your **Git Tags** as the single
7
7
  source of truth, enforcing a "Ground State" where your `package.json` version
8
8
  remains permanently at `0.0.0`.
9
9
 
10
+ HIPP provides **cryptographic signing** and **out-of-band verification** to
11
+ guarantee that the package in the npm registry exactly matches your git tag.
12
+
10
13
  ---
11
14
 
12
- ## 🧐 Why HIPP?
15
+ ## Why HIPP?
16
+
17
+ ### Integrity
13
18
 
14
19
  Traditional NPM versioning requires you to store your "Version of Truth" inside
15
20
  your source code files (`package.json`). This creates a **State Conflict** that
16
21
  leads to several systemic problems:
17
22
 
18
- ### 1. Integrity Failure in standard workflow
19
23
  `npm version` and `git tag` are two distinct, non-atomic actions. If you tag a
20
24
  commit but forget to update the JSON (or vice-versa), your registry package and
21
25
  your Git history diverge. This scenario makes it impossible to guarantee that
22
- the code in the registry matches the code at that tag.
26
+ the code in the registry matches the code at that tag.
23
27
 
24
28
  **HIPP ensures they are fundamentally linked by extracting the version directly
25
- from the Git Tag.**
29
+ from the Git Tag, and cryptographically signing the package contents.**
30
+
31
+ ### No "Chore" Noise
26
32
 
27
- ### 2. Chore" Noise & Merge Conflicts
28
33
  Every release usually requires a "chore: bump version" commit. When multiple
29
34
  branches are developed simultaneously, these version changes cause constant,
30
- trivial merge conflicts.
35
+ trivial merge conflicts.
31
36
 
32
37
  **HIPP makes your `package.json` version immutable (0.0.0), so it never
33
38
  conflicts and your git history stays clean.**
34
39
 
35
40
  ---
36
41
 
37
- ## 🛠 Usage
42
+ ## Usage
38
43
 
39
- ### 1. The Setup Set your project's `package.json` version to `0.0.0`.
40
- This is the **HIPP Doctrine**.
44
+ ### Setup
45
+
46
+ 1. Set your project's `package.json` version to `0.0.0`. This is the **HIPP Doctrine**.
41
47
 
42
48
  ```json
43
49
  { "name": "your-package", "version": "0.0.0" }
44
50
  ```
45
51
 
46
- ### 2. Tag and Publish with HIPP
52
+ 2. Ensure `package-lock.json` exists and is tracked by git.
53
+
54
+ ### Tag and Publish
47
55
 
48
56
  ```bash
49
57
  git tag v1.0.0
@@ -51,26 +59,82 @@ npx @dk/hipp
51
59
  ```
52
60
 
53
61
  HIPP will:
54
- 1. **Verify**: Ensure the `0.0.0` doctrine is being followed.
55
- 2. **Clean Check**: Ensure your git status is clean (no uncommitted local
56
- "drift").
57
- 3. **Validate**: Extract and verify the latest tag against Semver rules.
58
- 4. **Confirm**: Ask for a 🚀 confirmation before ignition.
59
- 5. **Restore**: Automatically return your local files to `0.0.0` after the
60
- smoke clears.
62
+
63
+ 1. **Key Generation**: Generate Ed25519 signing keys if needed (`hipp.priv`, `hipp.pub`)
64
+ 2. **Verify**: Ensure the `0.0.0` doctrine is being followed
65
+ 3. **Clean Check**: Ensure your git status is clean
66
+ 4. **Validate**: Extract and verify the latest tag against Semver rules
67
+ 5. **Sign**: Create a cryptographic manifest of your package content
68
+ 6. **Publish**: Publish to npm from a staging directory (never mutating your source)
69
+ 7. **Confirm**: Ask for confirmation before ignition (skip with `-y`)
70
+
71
+ ### Signing Keys
72
+
73
+ On first run, HIPP generates an Ed25519 keypair:
74
+
75
+ - **`hipp.priv`** - Your private signing key. **Never committed to git.** Added to `.gitignore` automatically.
76
+ - **`hipp.pub`** - Your public verification key. **Committed to git** automatically.
77
+
78
+ The private key holder can sign packages. The public key verifies signatures.
61
79
 
62
80
  ### Options
63
- * `-y, --yes`: Skip the confirmation (ideal for CI/CD pipelines).
64
81
 
65
- If you need to pass additional flags to npm publish (like access or a custom registry), use the -- separator:
66
- Bash
82
+ * `-y, --yes`: Skip the confirmation prompt (ideal for CI/CD pipelines).
83
+
84
+ To pass additional flags to npm publish (like access or a custom registry), use `--`:
85
+
86
+ ```bash
87
+ npx @dk/hipp -- --access public --tag beta
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Verification
67
93
 
68
- `npx @dk/hipp -- --access public --tag beta`
94
+ HIPP provides out-of-band verification to guarantee package integrity:
69
95
 
96
+ ```bash
97
+ npx @dk/hipp verify @dk/your-package[@version]
98
+ ```
99
+
100
+ ### How Verification Works
101
+
102
+ 1. **Fetch from npm**: Downloads the package tarball and extracts the manifest
103
+ 2. **Fetch from git**: Clones the repository at the tagged commit and extracts the public key
104
+ 3. **Hash Verification**: Computes the hash of the git content and compares with the manifest
105
+ 4. **Signature Verification**: Verifies the manifest signature using the public key
106
+
107
+ If both checks pass, you can be certain that:
108
+ - The package contents in npm exactly match the git tag
109
+ - The manifest was signed by the holder of the private key
110
+
111
+ ### Integrity Rules
112
+
113
+ HIPP enforces strict integrity rules:
114
+
115
+ - `package.json` version must be `0.0.0`
116
+ - `package-lock.json` must exist and be tracked by git
117
+ - `npm ci --ignore-scripts --dry-run` must succeed
118
+ - Repository must be clean
119
+ - HEAD must be on a branch with an upstream
120
+ - HEAD must exactly match upstream
121
+ - HEAD must have an exact v-prefixed semver tag
122
+ - The exact tag must exist on origin and match locally
123
+ - HEAD commit must be contained in an origin remote branch
124
+ - Only git-tracked files are staged
125
+ - Only staged `package.json` is rewritten
70
126
 
71
127
  ---
72
128
 
73
- ## ⚖️ License
129
+ ## Security
130
+
131
+ HIPP uses **Ed25519** public-key signatures. The private key never leaves your
132
+ machine. The public key is distributed via git. Anyone can verify a signed
133
+ package, but only private key holders can publish.
134
+
135
+ ---
136
+
137
+ ## License
74
138
 
75
139
  **0BSD** (BSD Zero Clause License) By Dmytri Kleiner <dev@dmytri.to>
76
140
 
@@ -83,5 +147,21 @@ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
83
147
  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
84
148
  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
85
149
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
86
- PERFORMANCE OF THIS SOFTWARE. ```
150
+ PERFORMANCE OF THIS SOFTWARE.
151
+ ```json
152
+ {
153
+ "origin": "git@github.com:dmytri/hipp.git",
154
+ "tag": "v0.1.8"
155
+ }
156
+ ```
157
+
158
+ ```npx @dk/hipp @dk/hipp@0.1.8```
87
159
 
160
+ <!-- HIPP-MANIFEST -->
161
+ ```json
162
+ {
163
+ "hash": "57d56d76a120bbc755700c3aa1f91f756e2cda09076dc0df522beb6a22018dca",
164
+ "signature": "eclO/AvbMfQMGS7g/vwX1DbB3dbm4XxWvlMFjo06KpE+d3w7KagtIv9klyjTazls74yGy3qcHhoy3i6/zWmZAg=="
165
+ }
166
+ ```
167
+ <!-- /HIPP-MANIFEST -->
package/hipp.js CHANGED
@@ -42,6 +42,162 @@ function sha256(input) {
42
42
  return crypto.createHash('sha256').update(input).digest('hex');
43
43
  }
44
44
 
45
+ function getPrivateKeyPath() {
46
+ return path.join(process.cwd(), 'hipp.priv');
47
+ }
48
+
49
+ function getPublicKeyPath() {
50
+ return path.join(process.cwd(), 'hipp.pub');
51
+ }
52
+
53
+ function generateKeyPair() {
54
+ const { generateKeyPairSync } = crypto;
55
+ const { privateKey, publicKey } = generateKeyPairSync('ed25519', {
56
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
57
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
58
+ });
59
+ return { privateKey, publicKey };
60
+ }
61
+
62
+ function loadOrGenerateKeys() {
63
+ const privPath = getPrivateKeyPath();
64
+ const pubPath = getPublicKeyPath();
65
+
66
+ if (fs.existsSync(privPath) && fs.existsSync(pubPath)) {
67
+ return {
68
+ privateKey: fs.readFileSync(privPath, 'utf8'),
69
+ publicKey: fs.readFileSync(pubPath, 'utf8'),
70
+ };
71
+ }
72
+
73
+ log.info('🔑 Generating Ed25519 keypair...');
74
+ const { privateKey, publicKey } = generateKeyPair();
75
+
76
+ fs.writeFileSync(privPath, privateKey, { mode: 0o600 });
77
+ fs.writeFileSync(pubPath, publicKey);
78
+
79
+ log.success('🔑 Keypair generated.');
80
+
81
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
82
+ let gitignore = '';
83
+ if (fs.existsSync(gitignorePath)) {
84
+ gitignore = fs.readFileSync(gitignorePath, 'utf8');
85
+ }
86
+ if (!gitignore.includes('hipp.priv')) {
87
+ fs.writeFileSync(gitignorePath, gitignore.trimEnd() + '\nhipp.priv\n');
88
+ log.info('📝 Added hipp.priv to .gitignore');
89
+ }
90
+
91
+ return { privateKey, publicKey };
92
+ }
93
+
94
+ function signContent(data, privateKey) {
95
+ const signature = crypto.sign(null, Buffer.from(data), {
96
+ key: privateKey,
97
+ dsaEncoding: 'ieee-p1363',
98
+ });
99
+ return signature.toString('base64');
100
+ }
101
+
102
+ function verifySignature(data, signature, publicKey) {
103
+ const verify = crypto.verify;
104
+ return verify(null, Buffer.from(data), {
105
+ key: publicKey,
106
+ dsaEncoding: 'ieee-p1363',
107
+ }, Buffer.from(signature, 'base64'));
108
+ }
109
+
110
+ function createManifest(hash, signature) {
111
+ return JSON.stringify({ hash, signature }, null, 2);
112
+ }
113
+
114
+ function parseManifest(manifestStr) {
115
+ try {
116
+ return JSON.parse(manifestStr);
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ function buildSignData(hash, origin, tag) {
123
+ return `${hash}\n${origin}\n${tag}\n`;
124
+ }
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) {
166
+ const lines = readmeContent.split('\n');
167
+ let jsonStart = -1;
168
+ let braceCount = 0;
169
+ let inJson = false;
170
+
171
+ for (let i = 0; i < lines.length; i++) {
172
+ const line = lines[i];
173
+ if (line === '```json') {
174
+ jsonStart = i + 1;
175
+ inJson = true;
176
+ braceCount = 0;
177
+ continue;
178
+ }
179
+ if (inJson) {
180
+ for (const char of line) {
181
+ if (char === '{') braceCount++;
182
+ if (char === '}') braceCount--;
183
+ }
184
+ if (braceCount === 0 && line.includes('}')) {
185
+ const jsonStr = lines.slice(jsonStart, i + 1).join('\n');
186
+ try {
187
+ const parsed = JSON.parse(jsonStr);
188
+ if (parsed.origin && parsed.tag) {
189
+ return parsed;
190
+ }
191
+ } catch {
192
+ // continue searching
193
+ }
194
+ inJson = false;
195
+ }
196
+ }
197
+ }
198
+ return null;
199
+ }
200
+
45
201
  function safeStageName(name) {
46
202
  return name.replace(/[^a-zA-Z0-9._-]/g, '-');
47
203
  }
@@ -237,6 +393,136 @@ function copyTrackedFiles(stageDir, files) {
237
393
  }
238
394
  }
239
395
 
396
+ async function runVerify(packageSpec) {
397
+ let pkgName, pkgVersion;
398
+ if (packageSpec.startsWith('@')) {
399
+ const atIndex = packageSpec.indexOf('@', 1);
400
+ if (atIndex === -1) {
401
+ pkgName = packageSpec;
402
+ pkgVersion = undefined;
403
+ } else {
404
+ pkgName = packageSpec.slice(0, atIndex);
405
+ pkgVersion = packageSpec.slice(atIndex + 1);
406
+ }
407
+ } else {
408
+ const atIndex = packageSpec.indexOf('@');
409
+ if (atIndex === -1) {
410
+ pkgName = packageSpec;
411
+ pkgVersion = undefined;
412
+ } else {
413
+ pkgName = packageSpec.slice(0, atIndex);
414
+ pkgVersion = packageSpec.slice(atIndex + 1);
415
+ }
416
+ }
417
+ log.info(`🔍 HIPP Verify: ${pkgName}${pkgVersion ? '@' + pkgVersion : ''}`);
418
+
419
+ const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(pkgName)}/${pkgVersion || 'latest'}`;
420
+
421
+ log.info(`📦 Fetching from npm...`);
422
+ const registryJson = runCmd('curl', ['-s', '-L', registryUrl]);
423
+ let tarballUrl;
424
+ try {
425
+ const json = JSON.parse(registryJson.stdout.trim());
426
+ tarballUrl = json.dist.tarball;
427
+ } catch {
428
+ fail(`❌ Failed to parse npm registry response for ${pkgName}`);
429
+ }
430
+
431
+ const tarballPath = path.join(os.tmpdir(), `hipp-verify-${safeStageName(pkgName)}-tgz`);
432
+ const extractDir = path.join(os.tmpdir(), `hipp-verify-extract-${safeStageName(pkgName)}`);
433
+
434
+ try {
435
+ log.info(`📦 Downloading tarball from ${tarballUrl}...`);
436
+ const curlResult = runCmd('curl', ['-s', '-L', '-o', tarballPath, tarballUrl]);
437
+ if (curlResult.status !== 0) {
438
+ fail(`❌ Failed to download tarball`);
439
+ }
440
+
441
+ if (fs.existsSync(extractDir)) {
442
+ fs.rmSync(extractDir, { recursive: true });
443
+ }
444
+ fs.mkdirSync(extractDir, { recursive: true });
445
+
446
+ log.info(`📦 Extracting tarball...`);
447
+ const tarResult = spawnSync('tar', ['-xzf', tarballPath, '-C', extractDir], { encoding: 'utf8', stdio: 'pipe' });
448
+ if (tarResult.status !== 0) {
449
+ fail(`❌ Failed to extract tarball: ${tarResult.stderr}`);
450
+ }
451
+
452
+ const packageDir = path.join(extractDir, 'package');
453
+ const stagedReadmePath = path.join(packageDir, 'README.md');
454
+
455
+ if (!fs.existsSync(stagedReadmePath)) {
456
+ fail(`❌ README.md not found in package at ${stagedReadmePath}`);
457
+ }
458
+
459
+ const stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
460
+ const jsonMeta = extractJsonMetaFromReadme(stagedReadme);
461
+ if (!jsonMeta || !jsonMeta.origin || !jsonMeta.tag) {
462
+ fail(`❌ JSON meta (origin/tag) not found in README`);
463
+ }
464
+
465
+ const manifestBase64 = extractManifestFromReadme(stagedReadme);
466
+ if (!manifestBase64) {
467
+ fail(`❌ Manifest not found in README`);
468
+ }
469
+
470
+ const manifest = parseManifest(manifestBase64);
471
+ if (!manifest || !manifest.hash || !manifest.signature) {
472
+ fail(`❌ Invalid manifest format`);
473
+ }
474
+
475
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
476
+ try {
477
+ log.info(`🌿 Fetching from git origin...`);
478
+
479
+ const originUrl = jsonMeta.origin;
480
+ const tag = jsonMeta.tag;
481
+
482
+ git(['clone', '--branch', tag, '--depth', '1', originUrl, tmpDir], { stdio: 'pipe' });
483
+
484
+ const clonedReadmePath = path.join(tmpDir, 'README.md');
485
+ if (!fs.existsSync(clonedReadmePath)) {
486
+ fail(`❌ README.md not found in git at tag ${tag}`);
487
+ }
488
+
489
+ const clonedReadme = fs.readFileSync(clonedReadmePath, 'utf8');
490
+ const clonedHash = computeReadmeHash(clonedReadme);
491
+
492
+ if (clonedHash !== manifest.hash) {
493
+ fail(`❌ Hash mismatch: git content does not match npm manifest`);
494
+ }
495
+
496
+ log.success(`🔒 Content hash verified: ${manifest.hash.slice(0, 12)}...`);
497
+
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
+ const signData = buildSignData(manifest.hash, originUrl, tag);
505
+ const signatureValid = verifySignature(signData, manifest.signature, publicKey);
506
+
507
+ if (!signatureValid) {
508
+ fail(`❌ Signature verification failed`);
509
+ }
510
+
511
+ log.success(`🔏 Signature verified`);
512
+ log.success(`✅ Package ${pkgName} verified successfully!`);
513
+ log.info(`📍 Origin: ${originUrl}`);
514
+ log.info(`📍 Tag: ${tag}`);
515
+ } finally {
516
+ fs.rmSync(tmpDir, { recursive: true, force: true });
517
+ }
518
+ } finally {
519
+ fs.rmSync(tarballPath, { recursive: true, force: true });
520
+ if (fs.existsSync(extractDir)) {
521
+ fs.rmSync(extractDir, { recursive: true, force: true });
522
+ }
523
+ }
524
+ }
525
+
240
526
  async function confirmPrompt(name, version) {
241
527
  const rl = readline.createInterface({
242
528
  input: process.stdin,
@@ -266,6 +552,19 @@ async function run() {
266
552
  }
267
553
 
268
554
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
555
+
556
+ loadOrGenerateKeys();
557
+
558
+ const pubPath = getPublicKeyPath();
559
+ try {
560
+ git(['ls-files', '--error-unmatch', 'hipp.pub']);
561
+ } catch {
562
+ log.info('📝 Committing hipp.pub to repo...');
563
+ git(['add', 'hipp.pub']);
564
+ git(['commit', '-m', 'Add hipp public key for package signing']);
565
+ log.success('📝 hipp.pub committed.');
566
+ }
567
+
269
568
  ensureCleanRepo(pkg);
270
569
 
271
570
  const { rawTag, version } = getVersionFromExactTagOnHead();
@@ -296,11 +595,31 @@ async function run() {
296
595
  log.info(`🏗️ Staging tracked files to ${stageDir}...`);
297
596
  copyTrackedFiles(stageDir, trackedFiles);
298
597
 
598
+ const { privateKey } = loadOrGenerateKeys();
599
+
299
600
  const stagedPkgPath = path.join(stageDir, 'package.json');
300
601
  const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
301
602
  stagedPkg.version = version;
302
603
  fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
303
604
 
605
+ const stagedReadmePath = path.join(stageDir, 'README.md');
606
+ let stagedReadme = '';
607
+ if (fs.existsSync(stagedReadmePath)) {
608
+ stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
609
+ }
610
+
611
+ stagedReadme += '\n```json\n{\n "origin": "' + provenance.remoteUrl + '",\n "tag": "' + rawTag + '"\n}\n```\n\n```npx @dk/hipp ' + pkg.name + '@' + version + '```\n\n';
612
+
613
+ const readmeHash = computeReadmeHash(stagedReadme);
614
+ const dataToSign = buildSignData(readmeHash, provenance.remoteUrl, rawTag);
615
+ const signature = signContent(dataToSign, privateKey);
616
+ const manifest = createManifest(readmeHash, signature);
617
+ stagedReadme = appendManifestToReadme(stagedReadme, manifest);
618
+
619
+ fs.writeFileSync(stagedReadmePath, stagedReadme);
620
+
621
+ log.success('🔏 Manifest signed.');
622
+
304
623
  log.info('🔥 Ignition...');
305
624
 
306
625
  const result = spawnSync('npm', ['publish', ...npmArgs], {
@@ -330,11 +649,18 @@ async function run() {
330
649
  }
331
650
  }
332
651
 
333
- if (process.argv.includes('--help') || process.argv.includes('-h')) {
652
+ const isVerify = process.argv.includes('verify');
653
+ const verifyIndex = process.argv.indexOf('verify');
654
+ const packageSpec = verifyIndex !== -1 ? process.argv[verifyIndex + 1] : null;
655
+
656
+ if (isVerify && packageSpec) {
657
+ runVerify(packageSpec);
658
+ } else if (process.argv.includes('--help') || process.argv.includes('-h')) {
334
659
  console.log(`\x1b[36mHIPP - High Integrity Package Publisher\x1b[0m
335
660
 
336
661
  Usage:
337
662
  npx hipp [options] [-- npm-options]
663
+ npx hipp verify <package>[@version]
338
664
 
339
665
  Options:
340
666
  -y, --yes Skip confirmation prompt
package/hipp.pub ADDED
@@ -0,0 +1,3 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MCowBQYDK2VwAyEA4/mM4ila/URYTYCmHIA33cuDFWtauGHo6TImvHimVa8=
3
+ -----END PUBLIC KEY-----
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dk/hipp",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "High Integrity Package Publisher",
5
5
  "main": "hipp.js",
6
6
  "bin": {