@reneturcios/npmpreflight 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rene Turcios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,151 @@
1
- # Temporary Holding Version
1
+ # npmpreflight
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Audit a package directory before `npm publish`, while a bad publish is
4
+ still cheap to undo.
5
+
6
+ npmpreflight resolves the file set your package would actually ship,
7
+ scans the shipped files for secret patterns, totals the size, and checks
8
+ `package.json` for the fields and entry points a publish depends on. It
9
+ answers the questions that are embarrassing to discover after a release:
10
+ is a credential sitting in a shipped file, does the `bin` target exist
11
+ in the tarball, is the license field set, and is a scoped package about
12
+ to publish as restricted.
13
+
14
+ ## Install
15
+
16
+ Run it without installing:
17
+
18
+ ```sh
19
+ npx @reneturcios/npmpreflight check .
20
+ ```
21
+
22
+ Or install it globally:
23
+
24
+ ```sh
25
+ npm install -g @reneturcios/npmpreflight
26
+ npmpreflight check .
27
+ ```
28
+
29
+ Requires Node 20 or newer.
30
+
31
+ ## CLI usage
32
+
33
+ ```sh
34
+ npmpreflight check [path] [--json]
35
+ npmpreflight mcp
36
+ npmpreflight --version
37
+ ```
38
+
39
+ - `check [path]` audits the package directory at `path` (default: the
40
+ current directory) and prints a human readable report. Add `--json`
41
+ for the machine readable report.
42
+ - `mcp` starts the MCP server over stdio (see below).
43
+ - `--version` prints the version.
44
+
45
+ Exit codes: `0` when the verdict is READY, `1` for any other verdict,
46
+ `2` for a usage error or an unreadable or invalid `package.json`, and
47
+ `141` when the reader closed the output pipe early.
48
+
49
+ Example output:
50
+
51
+ ```text
52
+ npmpreflight check .: FIX-FIRST
53
+ files: 4, bytes: 603, findings: 2 (high: 2, medium: 0, low: 0, info: 0)
54
+ summary: 4 shipped file(s), 603 byte(s), 2 finding(s) (2 high, 0 medium, 0 low, 0 info). High-severity findings must be fixed before publishing.
55
+ [HIGH] SECRET-LEAK src/index.js:2 Shipped file matches the secret pattern private-key-block. Remove the credential from the package and rotate it if it was ever real. (evidence: private-key-block)
56
+ [HIGH] SECRET-LEAK src/index.js:3 Shipped file matches the secret pattern github-pat-classic. Remove the credential from the package and rotate it if it was ever real. (evidence: github-pat-classic)
57
+ FIX-FIRST: resolve the high-severity findings before publishing.
58
+ ```
59
+
60
+ Findings never include the matched secret text. The evidence for a
61
+ secret finding is the pattern id only, plus the file and line, so a
62
+ report is safe to paste into an issue or a CI log.
63
+
64
+ ## MCP
65
+
66
+ The same audit is available as an MCP tool over stdio:
67
+
68
+ - Tool: `preflight_package`
69
+ - Input: `path`, the package directory to check
70
+ - Output: the JSON report, identical to `check --json`
71
+
72
+ Start it with `npmpreflight mcp`, or configure an MCP client to run
73
+ `npx @reneturcios/npmpreflight mcp`. The server is also published as an
74
+ OCI image and listed in `server.json` under the name
75
+ `io.github.CloudCorpRecords/npmpreflight`.
76
+
77
+ ## Checks
78
+
79
+ | Rule | Severity | What it flags |
80
+ | --- | --- | --- |
81
+ | MISSING-FIELD | high | `package.json` is missing one of `name`, `version`, `license`, or `description` (one finding per missing field). |
82
+ | BIN-MISSING | high | A `bin` path in `package.json` is not in the shipped file set. |
83
+ | MAIN-MISSING | high | The `main` entry point is not in the shipped file set. |
84
+ | SECRET-LEAK | high | A shipped text file matches a secret pattern: a private key block header, an AWS access key id shape (`AKIA` plus 16 characters), a classic GitHub token shape (`ghp_` plus 36 or more characters), a fine grained GitHub token shape (`github_pat_` prefix), an `sk-` API key shape, or a generic `api_key` style assignment with a long quoted value. Evidence is the pattern id only. |
85
+ | OVERSIZE | medium | Total shipped size over 10 MiB, or any single shipped file over 1 MiB (one finding per oversize file). |
86
+ | SCOPED-NO-PUBLISHCONFIG | medium | The package name is scoped (starts with `@`) and `publishConfig.access` is not `public`. |
87
+ | NO-README | low | No README file is in the shipped set. |
88
+ | NO-ENGINES | info | `package.json` declares no `engines` range. |
89
+
90
+ Every report counts the shipped files and their total bytes, plus the
91
+ finding totals by severity.
92
+
93
+ ## Verdict
94
+
95
+ - `READY` when there are no high severity findings. Medium, low, and
96
+ info findings are reported but do not block the verdict.
97
+ - `FIX-FIRST` when at least one high severity finding exists.
98
+
99
+ ## How the shipped file set is resolved
100
+
101
+ - If `package.json` has a `files` array, those paths are included:
102
+ files are added directly and directories are walked recursively.
103
+ `package.json`, `README`, and `LICENSE` are always included when
104
+ present, whether or not they are listed, matching npm.
105
+ - If there is no `files` array, everything in the directory is included
106
+ except `node_modules`, `.git`, and paths matched by `.npmignore`
107
+ when one is present. The always included files above are still
108
+ included even if an ignore rule would match them.
109
+ - The walk is capped at 5000 shipped files. When the cap is reached
110
+ the report records it in its counts (`truncated: 1`, with
111
+ `filesSeen` and `maxFiles`) and the render notes that the remaining
112
+ files were not scanned.
113
+
114
+ ## Limits
115
+
116
+ Read these before trusting a clean verdict:
117
+
118
+ - The file set is a static resolution, not a byte-exact `npm pack`.
119
+ npm applies more rules (its own always excluded files, lifecycle
120
+ generated files, and publish time behaviour) that a static pass does
121
+ not reproduce. Treat npmpreflight as a preflight check, then confirm
122
+ a release with `npm pack --dry-run`.
123
+ - `.npmignore` support is a simple subset: blank lines, `#` comments,
124
+ plain lines (a file name at any depth, or an exact relative path),
125
+ and directory patterns ending in `/`. Globs, negations, and
126
+ character classes in `.npmignore` are not supported and such lines
127
+ will not match the way npm would match them.
128
+ - `files` entries support literal files, literal directories, simple
129
+ `*` and `**` globs, and `!` negations of those. Anything more exotic
130
+ is resolved literally and may differ from npm.
131
+ - Secret scanning reads text files only. A file whose scanned head
132
+ contains a NUL byte is treated as binary and skipped. Only the first
133
+ 512 KiB of each file is scanned, so a secret past that offset in a
134
+ large file will not be found. Patterns are shapes, not proof: a
135
+ match can be a test fixture or an example, and a real credential in
136
+ an unrecognized shape will not match.
137
+ - Symlinks are not followed and are not shipped.
138
+ - Output is deterministic: findings are sorted, counts are stable,
139
+ and no timestamps are emitted.
140
+
141
+ ## Development
142
+
143
+ ```sh
144
+ npm install
145
+ npm run build
146
+ npm test
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npmpreflight CLI.
4
+ *
5
+ * npmpreflight check [target] [--json]
6
+ * npmpreflight mcp
7
+ * npmpreflight --version
8
+ *
9
+ * Exit codes: 0 when the verdict is READY, 1 for any other verdict,
10
+ * 2 usage error, 141 when the reader closed the output pipe early
11
+ * (the shell-visible equivalent of death by SIGPIPE). The emit contract
12
+ * follows FACTORY-NOTES.md: one flush-aware write, exitCode never
13
+ * process.exit on report paths.
14
+ */
15
+ import { runCheck, renderReport, CLEAN_VERDICT, VERSION } from "./core.js";
16
+ function exitBrokenPipe() {
17
+ process.exit(141);
18
+ }
19
+ process.stdout.on("error", (err) => {
20
+ if (err.code === "EPIPE")
21
+ exitBrokenPipe();
22
+ throw err;
23
+ });
24
+ async function emit(text, code) {
25
+ process.exitCode = code;
26
+ await new Promise((resolve, reject) => {
27
+ process.stdout.write(`${text}\n`, (err) => {
28
+ if (!err) {
29
+ resolve();
30
+ return;
31
+ }
32
+ if (err.code === "EPIPE")
33
+ exitBrokenPipe();
34
+ reject(err);
35
+ });
36
+ });
37
+ }
38
+ function usage() {
39
+ console.error(`Usage:
40
+ npmpreflight check [target] [--json]
41
+ npmpreflight mcp
42
+ npmpreflight --version`);
43
+ process.exit(2);
44
+ }
45
+ async function main() {
46
+ const [cmd, ...rest] = process.argv.slice(2);
47
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") {
48
+ console.log(VERSION);
49
+ return;
50
+ }
51
+ if (cmd === "mcp") {
52
+ await import("./mcp.js");
53
+ return;
54
+ }
55
+ if (cmd !== "check")
56
+ usage();
57
+ const target = rest.find((a) => !a.startsWith("--")) ?? ".";
58
+ try {
59
+ const report = runCheck(target);
60
+ const text = rest.includes("--json") ? JSON.stringify(report, null, 2) : renderReport(report);
61
+ await emit(text, report.verdict === CLEAN_VERDICT ? 0 : 1);
62
+ }
63
+ catch (err) {
64
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
65
+ process.exitCode = 2;
66
+ }
67
+ }
68
+ void main();
69
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE3E,SAAS,cAAc;IACrB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpB,CAAC;AAED,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;IACxD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,cAAc,EAAE,CAAC;IAC3C,MAAM,GAAG,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI,CAAC,IAAY,EAAE,IAAY;IAC5C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACxB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;YACxC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YACD,IAAK,GAA6B,CAAC,IAAI,KAAK,OAAO;gBAAE,cAAc,EAAE,CAAC;YACtE,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,KAAK;IACZ,OAAO,CAAC,KAAK,CAAC;;;yBAGS,CAAC,CAAC;IACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;QAClB,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,OAAO;QAAE,KAAK,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC;IAC5D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9F,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { Report } from "./types.js";
2
+ export declare const VERSION = "0.1.0";
3
+ export declare const CLEAN_VERDICT = "READY";
4
+ export declare const FIX_VERDICT = "FIX-FIRST";
5
+ export declare const MAX_FILES = 5000;
6
+ export declare const MAX_SCAN_BYTES: number;
7
+ export declare const MAX_FILE_BYTES: number;
8
+ export declare const MAX_TOTAL_BYTES: number;
9
+ export declare function runCheck(target: string): Report;
10
+ export declare function renderReport(report: Report): string;
package/dist/core.js ADDED
@@ -0,0 +1,580 @@
1
+ /**
2
+ * npmpreflight core: audit a package directory before npm publish.
3
+ *
4
+ * The shipped file set is resolved statically: the files field when one
5
+ * is present (literal files, directories walked recursively, and a
6
+ * simple glob subset), otherwise everything in the directory except
7
+ * node_modules, .git, and paths matched by a simple .npmignore subset.
8
+ * package.json, README, and LICENSE are always included when present,
9
+ * matching npm behaviour. This is a static approximation of npm pack,
10
+ * not a byte-exact reproduction of it.
11
+ *
12
+ * Secret scanning never records the matched text. A finding carries the
13
+ * rule id, the file, the line, and the pattern id as its only evidence.
14
+ */
15
+ import { existsSync, openSync, readSync, closeSync, readdirSync, readFileSync, statSync } from "node:fs";
16
+ import { basename, dirname, join, resolve } from "node:path";
17
+ export const VERSION = "0.1.0";
18
+ export const CLEAN_VERDICT = "READY";
19
+ export const FIX_VERDICT = "FIX-FIRST";
20
+ export const MAX_FILES = 5000;
21
+ export const MAX_SCAN_BYTES = 512 * 1024;
22
+ export const MAX_FILE_BYTES = 1024 * 1024;
23
+ export const MAX_TOTAL_BYTES = 10 * 1024 * 1024;
24
+ const SEVERITY_ORDER = { high: 0, medium: 1, low: 2, info: 3 };
25
+ const REQUIRED_FIELDS = ["name", "version", "license", "description"];
26
+ const ALWAYS_INCLUDED_RE = /^(?:package\.json|readme(?:\.[^/]*)?|licen[cs]e(?:\.[^/]*)?)$/i;
27
+ /* ------------------------------------------------------------------ */
28
+ /* Small helpers */
29
+ /* ------------------------------------------------------------------ */
30
+ function stripDotSlash(p) {
31
+ let out = p.trim();
32
+ while (out.startsWith("./"))
33
+ out = out.slice(2);
34
+ return out.replace(/^\/+/, "");
35
+ }
36
+ function isRecord(v) {
37
+ return typeof v === "object" && v !== null && !Array.isArray(v);
38
+ }
39
+ function isPresent(v) {
40
+ if (v === undefined || v === null)
41
+ return false;
42
+ if (typeof v === "string")
43
+ return v.trim().length > 0;
44
+ if (isRecord(v))
45
+ return Object.keys(v).length > 0;
46
+ return true;
47
+ }
48
+ /**
49
+ * Parse the supported .npmignore subset: blank lines and # comments are
50
+ * skipped, plain lines match a file name at any depth or an exact
51
+ * relative path, and lines ending in / match a directory. Globs,
52
+ * negations, and character classes are not supported.
53
+ */
54
+ function parseNpmignore(root) {
55
+ const path = join(root, ".npmignore");
56
+ if (!existsSync(path))
57
+ return [];
58
+ let text;
59
+ try {
60
+ text = readFileSync(path, "utf8");
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ const rules = [];
66
+ for (const raw of text.split(/\r?\n/)) {
67
+ const line = raw.trim();
68
+ if (!line || line.startsWith("#"))
69
+ continue;
70
+ const cleaned = line.replace(/^\/+/, "");
71
+ const isDir = cleaned.endsWith("/");
72
+ const pattern = isDir ? cleaned.replace(/\/+$/, "") : cleaned;
73
+ if (!pattern)
74
+ continue;
75
+ rules.push({ pattern, isDir, hasSlash: pattern.includes("/") });
76
+ }
77
+ return rules;
78
+ }
79
+ function matchesIgnore(rel, rules) {
80
+ const segments = rel.split("/");
81
+ for (const rule of rules) {
82
+ if (rule.isDir) {
83
+ if (rule.hasSlash) {
84
+ if (rel === rule.pattern || rel.startsWith(`${rule.pattern}/`))
85
+ return true;
86
+ }
87
+ else if (segments.includes(rule.pattern)) {
88
+ return true;
89
+ }
90
+ continue;
91
+ }
92
+ if (rule.hasSlash) {
93
+ if (rel === rule.pattern || rel.startsWith(`${rule.pattern}/`))
94
+ return true;
95
+ }
96
+ else if (basename(rel) === rule.pattern) {
97
+ return true;
98
+ }
99
+ }
100
+ return false;
101
+ }
102
+ /* ------------------------------------------------------------------ */
103
+ /* Simple glob subset for files entries */
104
+ /* ------------------------------------------------------------------ */
105
+ function globToRegExp(glob) {
106
+ let out = "^";
107
+ let i = 0;
108
+ while (i < glob.length) {
109
+ const ch = glob[i];
110
+ if (ch === "*") {
111
+ if (glob[i + 1] === "*") {
112
+ if (glob[i + 2] === "/") {
113
+ out += "(?:.*/)?";
114
+ i += 3;
115
+ }
116
+ else {
117
+ out += ".*";
118
+ i += 2;
119
+ }
120
+ }
121
+ else {
122
+ out += "[^/]*";
123
+ i += 1;
124
+ }
125
+ }
126
+ else if (ch === "?") {
127
+ out += "[^/]";
128
+ i += 1;
129
+ }
130
+ else {
131
+ out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
132
+ i += 1;
133
+ }
134
+ }
135
+ out += "$";
136
+ return new RegExp(out);
137
+ }
138
+ class Collector {
139
+ byRel = new Map();
140
+ seen = new Set();
141
+ truncated = false;
142
+ get filesSeen() {
143
+ return this.seen.size;
144
+ }
145
+ get files() {
146
+ return [...this.byRel.values()].sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
147
+ }
148
+ has(rel) {
149
+ return this.byRel.has(rel);
150
+ }
151
+ remove(rel) {
152
+ this.byRel.delete(rel);
153
+ }
154
+ removeUnder(dirRel) {
155
+ for (const rel of [...this.byRel.keys()]) {
156
+ if (rel === dirRel || rel.startsWith(`${dirRel}/`))
157
+ this.byRel.delete(rel);
158
+ }
159
+ }
160
+ /** Count a distinct candidate file, then ship it unless the cap hit. */
161
+ consider(rel, abs) {
162
+ if (this.seen.has(rel))
163
+ return;
164
+ this.seen.add(rel);
165
+ if (this.byRel.size >= MAX_FILES) {
166
+ this.truncated = true;
167
+ return;
168
+ }
169
+ let size = 0;
170
+ try {
171
+ size = statSync(abs).size;
172
+ }
173
+ catch {
174
+ return;
175
+ }
176
+ this.byRel.set(rel, { rel, abs, size });
177
+ }
178
+ /** Walk a directory recursively in sorted order. */
179
+ walk(dirAbs, dirRel, opts = {}) {
180
+ let entries;
181
+ try {
182
+ entries = readdirSync(dirAbs, { withFileTypes: true });
183
+ }
184
+ catch {
185
+ return;
186
+ }
187
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
188
+ for (const entry of entries) {
189
+ if (entry.name === "node_modules" || entry.name === ".git")
190
+ continue;
191
+ const abs = join(dirAbs, entry.name);
192
+ const rel = dirRel ? `${dirRel}/${entry.name}` : entry.name;
193
+ if (entry.isDirectory()) {
194
+ this.walk(abs, rel, opts);
195
+ }
196
+ else if (entry.isFile()) {
197
+ const always = dirRel === "" && ALWAYS_INCLUDED_RE.test(entry.name);
198
+ if (!always && opts.ignore && matchesIgnore(rel, opts.ignore))
199
+ continue;
200
+ this.consider(rel, abs);
201
+ }
202
+ // Symlinks and special files are not followed or shipped.
203
+ }
204
+ }
205
+ }
206
+ function collectShipped(root, pkg) {
207
+ const collector = new Collector();
208
+ const filesField = Array.isArray(pkg["files"]) ? pkg["files"] : undefined;
209
+ if (filesField) {
210
+ for (const rawEntry of filesField) {
211
+ if (typeof rawEntry !== "string")
212
+ continue;
213
+ let entry = stripDotSlash(rawEntry);
214
+ if (!entry)
215
+ continue;
216
+ const negated = entry.startsWith("!");
217
+ if (negated)
218
+ entry = stripDotSlash(entry.slice(1));
219
+ if (!entry)
220
+ continue;
221
+ if (entry.includes("*") || entry.includes("?")) {
222
+ const re = globToRegExp(entry);
223
+ if (negated) {
224
+ for (const f of collector.files) {
225
+ if (re.test(f.rel))
226
+ collector.remove(f.rel);
227
+ }
228
+ }
229
+ else {
230
+ // Walk the whole tree once per glob entry, matching paths.
231
+ const before = new Set(collector.files.map((f) => f.rel));
232
+ const walker = new Collector();
233
+ walker.walk(root, "");
234
+ for (const f of walker.files) {
235
+ if (re.test(f.rel))
236
+ collector.consider(f.rel, f.abs);
237
+ }
238
+ void before;
239
+ }
240
+ continue;
241
+ }
242
+ const clean = entry.replace(/\/+$/, "");
243
+ const abs = join(root, clean);
244
+ if (negated) {
245
+ collector.remove(clean);
246
+ collector.removeUnder(clean);
247
+ continue;
248
+ }
249
+ let st;
250
+ try {
251
+ st = statSync(abs);
252
+ }
253
+ catch {
254
+ continue;
255
+ }
256
+ if (st.isDirectory()) {
257
+ collector.walk(abs, clean);
258
+ }
259
+ else if (st.isFile()) {
260
+ collector.consider(clean, abs);
261
+ }
262
+ }
263
+ }
264
+ else {
265
+ const ignore = parseNpmignore(root);
266
+ collector.walk(root, "", { ignore });
267
+ }
268
+ // Always included, even when the files field or .npmignore omits them.
269
+ let rootEntries = [];
270
+ try {
271
+ rootEntries = readdirSync(root).sort();
272
+ }
273
+ catch {
274
+ rootEntries = [];
275
+ }
276
+ for (const name of rootEntries) {
277
+ if (!ALWAYS_INCLUDED_RE.test(name))
278
+ continue;
279
+ const abs = join(root, name);
280
+ try {
281
+ if (statSync(abs).isFile())
282
+ collector.consider(name, abs);
283
+ }
284
+ catch {
285
+ continue;
286
+ }
287
+ }
288
+ return { files: collector.files, filesSeen: collector.filesSeen, truncated: collector.truncated };
289
+ }
290
+ const SECRET_PATTERNS = [
291
+ { id: "private-key-block", make: () => /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g },
292
+ { id: "aws-access-key-id", make: () => /\bAKIA[0-9A-Z]{16}\b/g },
293
+ { id: "github-pat-classic", make: () => /\bghp_[A-Za-z0-9]{36,}\b/g },
294
+ { id: "github-pat-fine-grained", make: () => /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
295
+ { id: "api-key-sk", make: () => /\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}\b/g },
296
+ ];
297
+ const GENERIC_ASSIGNMENT = {
298
+ id: "generic-api-key-assignment",
299
+ make: () => /(?:\bapi[_-]?key\b|\bapikey\b|\bsecret[_-]?key\b|\baccess[_-]?token\b|\bauth[_-]?token\b)\s*[:=]\s*["'][^"'\r\n\s]{16,}["']/gi,
300
+ };
301
+ /** Read at most MAX_SCAN_BYTES from the head of a file. */
302
+ function readHead(abs, size) {
303
+ const cap = Math.min(size, MAX_SCAN_BYTES);
304
+ if (cap <= 0)
305
+ return Buffer.alloc(0);
306
+ const buf = Buffer.alloc(cap);
307
+ let fd;
308
+ try {
309
+ fd = openSync(abs, "r");
310
+ }
311
+ catch {
312
+ return Buffer.alloc(0);
313
+ }
314
+ try {
315
+ readSync(fd, buf, 0, cap, 0);
316
+ }
317
+ catch {
318
+ return Buffer.alloc(0);
319
+ }
320
+ finally {
321
+ closeSync(fd);
322
+ }
323
+ return buf;
324
+ }
325
+ function lineOf(text, index) {
326
+ let line = 1;
327
+ for (let i = 0; i < index && i < text.length; i++) {
328
+ if (text[i] === "\n")
329
+ line += 1;
330
+ }
331
+ return line;
332
+ }
333
+ function scanFile(file, findings) {
334
+ const head = readHead(file.abs, file.size);
335
+ if (head.length === 0)
336
+ return;
337
+ if (head.includes(0))
338
+ return; // Binary file: not scanned as text.
339
+ const text = head.toString("utf8");
340
+ const seen = new Set();
341
+ const specificLines = new Set();
342
+ for (const pattern of SECRET_PATTERNS) {
343
+ const re = pattern.make();
344
+ for (const match of text.matchAll(re)) {
345
+ const line = lineOf(text, match.index ?? 0);
346
+ specificLines.add(line);
347
+ const key = `${pattern.id}:${line}`;
348
+ if (seen.has(key))
349
+ continue;
350
+ seen.add(key);
351
+ findings.push({
352
+ rule: "SECRET-LEAK",
353
+ severity: "high",
354
+ file: file.rel,
355
+ line,
356
+ message: `Shipped file matches the secret pattern ${pattern.id}. Remove the credential from the package and rotate it if it was ever real.`,
357
+ evidence: pattern.id,
358
+ });
359
+ }
360
+ }
361
+ // A generic assignment on a line already flagged by a specific
362
+ // pattern would only duplicate the same leak, so it is skipped there.
363
+ const genericRe = GENERIC_ASSIGNMENT.make();
364
+ for (const match of text.matchAll(genericRe)) {
365
+ const line = lineOf(text, match.index ?? 0);
366
+ if (specificLines.has(line))
367
+ continue;
368
+ const key = `${GENERIC_ASSIGNMENT.id}:${line}`;
369
+ if (seen.has(key))
370
+ continue;
371
+ seen.add(key);
372
+ findings.push({
373
+ rule: "SECRET-LEAK",
374
+ severity: "high",
375
+ file: file.rel,
376
+ line,
377
+ message: `Shipped file matches the secret pattern ${GENERIC_ASSIGNMENT.id}. Remove the credential from the package and rotate it if it was ever real.`,
378
+ evidence: GENERIC_ASSIGNMENT.id,
379
+ });
380
+ }
381
+ }
382
+ /* ------------------------------------------------------------------ */
383
+ /* Main check */
384
+ /* ------------------------------------------------------------------ */
385
+ export function runCheck(target) {
386
+ const root = resolve(target);
387
+ const pkgPath = join(root, "package.json");
388
+ let pkgText;
389
+ try {
390
+ pkgText = readFileSync(pkgPath, "utf8");
391
+ }
392
+ catch {
393
+ // A target that is itself a package.json file is accepted too.
394
+ try {
395
+ if (basename(root) === "package.json") {
396
+ return runCheck(dirname(root));
397
+ }
398
+ }
399
+ catch {
400
+ // Fall through to the clear error below.
401
+ }
402
+ throw new Error(`package.json not found at ${pkgPath}: ${target} is not a package directory with a readable package.json`);
403
+ }
404
+ let pkg;
405
+ try {
406
+ const parsed = JSON.parse(pkgText);
407
+ if (!isRecord(parsed))
408
+ throw new Error("top level is not an object");
409
+ pkg = parsed;
410
+ }
411
+ catch (err) {
412
+ throw new Error(`package.json at ${pkgPath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
413
+ }
414
+ const { files, filesSeen, truncated } = collectShipped(root, pkg);
415
+ const shippedRels = new Set(files.map((f) => f.rel));
416
+ const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
417
+ const findings = [];
418
+ // Required fields.
419
+ for (const field of REQUIRED_FIELDS) {
420
+ if (!isPresent(pkg[field])) {
421
+ findings.push({
422
+ rule: "MISSING-FIELD",
423
+ severity: "high",
424
+ file: "package.json",
425
+ message: `package.json is missing the ${field} field. npm publish needs it, and registries and tooling read it as the package identity.`,
426
+ evidence: field,
427
+ });
428
+ }
429
+ }
430
+ // Bin targets must be in the shipped set.
431
+ const binPaths = [];
432
+ const bin = pkg["bin"];
433
+ if (typeof bin === "string")
434
+ binPaths.push(bin);
435
+ else if (isRecord(bin)) {
436
+ for (const v of Object.values(bin))
437
+ if (typeof v === "string")
438
+ binPaths.push(v);
439
+ }
440
+ for (const binPath of [...binPaths].sort()) {
441
+ const rel = stripDotSlash(binPath);
442
+ if (!shippedRels.has(rel)) {
443
+ findings.push({
444
+ rule: "BIN-MISSING",
445
+ severity: "high",
446
+ file: "package.json",
447
+ message: `bin target ${rel} is not in the shipped file set. The published package would install a command that does not exist.`,
448
+ evidence: rel,
449
+ });
450
+ }
451
+ }
452
+ // The main entry point must be in the shipped set.
453
+ if (typeof pkg["main"] === "string" && pkg["main"].trim()) {
454
+ const rel = stripDotSlash(pkg["main"]);
455
+ if (!shippedRels.has(rel)) {
456
+ findings.push({
457
+ rule: "MAIN-MISSING",
458
+ severity: "high",
459
+ file: "package.json",
460
+ message: `main entry ${rel} is not in the shipped file set. Consumers would fail to load the published package.`,
461
+ evidence: rel,
462
+ });
463
+ }
464
+ }
465
+ // Secret scan over the shipped set.
466
+ for (const file of files)
467
+ scanFile(file, findings);
468
+ // Size checks.
469
+ for (const file of files) {
470
+ if (file.size > MAX_FILE_BYTES) {
471
+ findings.push({
472
+ rule: "OVERSIZE",
473
+ severity: "medium",
474
+ file: file.rel,
475
+ message: `Shipped file is ${file.size} bytes, over the ${MAX_FILE_BYTES} byte per-file limit. Large payloads usually belong in a release asset, not the npm tarball.`,
476
+ evidence: `${file.size} bytes`,
477
+ });
478
+ }
479
+ }
480
+ if (totalBytes > MAX_TOTAL_BYTES) {
481
+ findings.push({
482
+ rule: "OVERSIZE",
483
+ severity: "medium",
484
+ message: `Total shipped size is ${totalBytes} bytes, over the ${MAX_TOTAL_BYTES} byte limit. Trim the files field or split the payload out of the package.`,
485
+ evidence: `${totalBytes} bytes`,
486
+ });
487
+ }
488
+ // Scoped packages need an explicit public publishConfig.
489
+ const name = typeof pkg["name"] === "string" ? pkg["name"] : "";
490
+ if (name.startsWith("@")) {
491
+ const pc = isRecord(pkg["publishConfig"]) ? pkg["publishConfig"] : undefined;
492
+ if (pc?.["access"] !== "public") {
493
+ findings.push({
494
+ rule: "SCOPED-NO-PUBLISHCONFIG",
495
+ severity: "medium",
496
+ file: "package.json",
497
+ message: `Scoped package ${name} does not set publishConfig.access to public. A first publish would default to restricted access and fail or stay private.`,
498
+ evidence: name,
499
+ });
500
+ }
501
+ }
502
+ // README presence.
503
+ const hasReadme = files.some((f) => /^readme(?:\.[^/]*)?$/i.test(basename(f.rel)));
504
+ if (!hasReadme) {
505
+ findings.push({
506
+ rule: "NO-README",
507
+ severity: "low",
508
+ message: "No README file is in the shipped set. The registry page and the installed package would have no usage documentation.",
509
+ });
510
+ }
511
+ // Engines declaration.
512
+ if (!isPresent(pkg["engines"])) {
513
+ findings.push({
514
+ rule: "NO-ENGINES",
515
+ severity: "info",
516
+ file: "package.json",
517
+ message: "package.json declares no engines range. Consumers and CI cannot tell which Node versions the package supports.",
518
+ });
519
+ }
520
+ findings.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity] ||
521
+ (a.file ?? "").localeCompare(b.file ?? "") ||
522
+ (a.line ?? 0) - (b.line ?? 0) ||
523
+ (a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : 0) ||
524
+ (a.message < b.message ? -1 : a.message > b.message ? 1 : 0));
525
+ const bySeverity = { high: 0, medium: 0, low: 0, info: 0 };
526
+ for (const f of findings)
527
+ bySeverity[f.severity] += 1;
528
+ const verdict = bySeverity.high > 0 ? FIX_VERDICT : CLEAN_VERDICT;
529
+ const counts = {
530
+ files: files.length,
531
+ bytes: totalBytes,
532
+ findings: findings.length,
533
+ high: bySeverity.high,
534
+ medium: bySeverity.medium,
535
+ low: bySeverity.low,
536
+ info: bySeverity.info,
537
+ filesSeen,
538
+ truncated: truncated ? 1 : 0,
539
+ maxFiles: MAX_FILES,
540
+ maxScanBytes: MAX_SCAN_BYTES,
541
+ };
542
+ const summary = `${files.length} shipped file(s), ${totalBytes} byte(s), ${findings.length} finding(s) ` +
543
+ `(${bySeverity.high} high, ${bySeverity.medium} medium, ${bySeverity.low} low, ${bySeverity.info} info). ` +
544
+ (verdict === CLEAN_VERDICT ? "No high-severity findings." : "High-severity findings must be fixed before publishing.");
545
+ return {
546
+ tool: "npmpreflight",
547
+ version: VERSION,
548
+ target,
549
+ verdict,
550
+ counts,
551
+ findings,
552
+ summary,
553
+ };
554
+ }
555
+ /* ------------------------------------------------------------------ */
556
+ /* Rendering */
557
+ /* ------------------------------------------------------------------ */
558
+ export function renderReport(report) {
559
+ const out = [];
560
+ out.push(`npmpreflight check ${report.target}: ${report.verdict}`);
561
+ out.push(` files: ${report.counts["files"] ?? 0}, bytes: ${report.counts["bytes"] ?? 0}, findings: ${report.counts["findings"] ?? 0} ` +
562
+ `(high: ${report.counts["high"] ?? 0}, medium: ${report.counts["medium"] ?? 0}, low: ${report.counts["low"] ?? 0}, info: ${report.counts["info"] ?? 0})`);
563
+ out.push(` summary: ${report.summary}`);
564
+ if ((report.counts["truncated"] ?? 0) > 0) {
565
+ out.push(` note: shipped file set cap reached: ${report.counts["filesSeen"] ?? 0} file(s) seen, only the first ${report.counts["maxFiles"] ?? MAX_FILES} (sorted by path within each directory) were shipped and scanned. The remaining files were NOT scanned.`);
566
+ }
567
+ for (const f of report.findings) {
568
+ const where = f.file ? (f.line ? `${f.file}:${f.line}` : f.file) : "package";
569
+ const evidence = f.evidence ? ` (evidence: ${f.evidence})` : "";
570
+ out.push(` [${f.severity.toUpperCase()}] ${f.rule} ${where} ${f.message}${evidence}`);
571
+ }
572
+ if (report.verdict === CLEAN_VERDICT) {
573
+ out.push("READY: no high-severity findings in the shipped file set.");
574
+ }
575
+ else {
576
+ out.push("FIX-FIRST: resolve the high-severity findings before publishing.");
577
+ }
578
+ return out.join("\n");
579
+ }
580
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { runCheck, renderReport, CLEAN_VERDICT, VERSION } from "./core.js";
2
+ export type { Finding, Report, Severity } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { runCheck, renderReport, CLEAN_VERDICT, VERSION } from "./core.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/mcp.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * npmpreflight MCP server. Tool: preflight_package.
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import { runCheck, VERSION } from "./core.js";
8
+ const server = new McpServer({ name: "npmpreflight", version: VERSION });
9
+ server.tool("preflight_package", "Audit a package directory before publishing: resolves the shipped file set from the files field and ignore rules, scans shipped files for secret patterns (private keys, token shapes), totals the size, and checks package.json for required fields, a bin target that exists, license, and publishConfig on scoped names. Verdict READY or FIX-FIRST with evidence.", {
10
+ path: z.string().describe("Target path to check"),
11
+ }, async ({ path }) => {
12
+ const report = runCheck(path);
13
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
14
+ });
15
+ async function main() {
16
+ await server.connect(new StdioServerTransport());
17
+ }
18
+ void main();
19
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAEzE,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,uWAAuW,EACvW;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;CAClD,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type Severity = "info" | "low" | "medium" | "high";
2
+ export interface Finding {
3
+ rule: string;
4
+ severity: Severity;
5
+ file?: string;
6
+ line?: number;
7
+ message: string;
8
+ evidence?: string;
9
+ }
10
+ export interface Report {
11
+ tool: string;
12
+ version: string;
13
+ target: string;
14
+ verdict: string;
15
+ counts: Record<string, number>;
16
+ findings: Finding[];
17
+ summary: string;
18
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
+ "bin": {
3
+ "npmpreflight": "dist/cli.js"
4
+ },
5
+ "dependencies": {
6
+ "@modelcontextprotocol/sdk": "^1",
7
+ "zod": "^3"
8
+ },
9
+ "description": "Pre-publish npm package auditor: what npm pack would ship, total size, secret-pattern leak scan over the shipped file set, and missing or risky package.json fields, checked before you publish instead of after.",
10
+ "devDependencies": {
11
+ "@types/node": "^20",
12
+ "tsx": "^4",
13
+ "typescript": "^5"
14
+ },
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "server.json"
21
+ ],
22
+ "homepage": "https://github.com/CloudCorpRecords/npmpreflight#readme",
23
+ "license": "MIT",
24
+ "mcpName": "io.github.CloudCorpRecords/npmpreflight",
2
25
  "name": "@reneturcios/npmpreflight",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
26
+ "private": false,
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/CloudCorpRecords/npmpreflight.git"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "dev": "tsx src/cli.ts",
37
+ "test": "tsx --test test/*.test.ts"
38
+ },
39
+ "type": "module",
40
+ "version": "0.1.0"
41
+ }
package/server.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "description": "Pre-publish npm pack auditor: shipped files, size, secret-pattern leaks, missing fields.",
4
+ "name": "io.github.CloudCorpRecords/npmpreflight",
5
+ "packages": [
6
+ {
7
+ "identifier": "@reneturcios/npmpreflight",
8
+ "packageArguments": [
9
+ {
10
+ "type": "positional",
11
+ "value": "mcp"
12
+ }
13
+ ],
14
+ "registryBaseUrl": "https://registry.npmjs.org",
15
+ "registryType": "npm",
16
+ "runtimeHint": "npx",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "version": "0.1.0"
21
+ },
22
+ {
23
+ "identifier": "ghcr.io/cloudcorprecords/npmpreflight:0.1.0",
24
+ "packageArguments": [
25
+ {
26
+ "type": "positional",
27
+ "value": "mcp"
28
+ }
29
+ ],
30
+ "registryType": "oci",
31
+ "runtimeArguments": [
32
+ {
33
+ "name": "--rm",
34
+ "type": "named"
35
+ },
36
+ {
37
+ "name": "-i",
38
+ "type": "named"
39
+ }
40
+ ],
41
+ "runtimeHint": "docker",
42
+ "transport": {
43
+ "type": "stdio"
44
+ }
45
+ }
46
+ ],
47
+ "repository": {
48
+ "source": "github",
49
+ "url": "https://github.com/CloudCorpRecords/npmpreflight"
50
+ },
51
+ "version": "0.1.0"
52
+ }