nativekeel 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 NativeKeel
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 ADDED
@@ -0,0 +1,101 @@
1
+ # NativeKeel
2
+
3
+ Health check for React Native and Expo apps. One command, no install, no account:
4
+
5
+ ```sh
6
+ npx nativekeel
7
+ ```
8
+
9
+ It answers the questions that decide whether your app can still ship:
10
+
11
+ - **Is your React Native / Expo version still supported?** React Native supports the latest minor and the two before it.
12
+ - **Can you upgrade at all?** The legacy architecture was removed in 0.82. If the New Architecture is off, you are stuck at 0.81.
13
+ - **Which packages block the upgrade?** Native modules without New Architecture support, unmaintained or with no release in years, with known alternatives. Outdated "unmaintained" flags are cross-checked against the latest npm release.
14
+ - **What can you simply delete?** Packages that are installed but never imported, referenced natively or required by another package. Every one you remove is one you never upgrade.
15
+ - **Will the stores accept your next build?** Google Play `targetSdk` and 16 KB page size (it opens your built APK/AAB and checks the alignment of every native library), and the iOS privacy manifest.
16
+ - **Are secrets shipped inside your app?** AWS, Stripe, OpenAI, Anthropic, GitHub, Slack, SendGrid, Twilio, Shopify, Google OAuth keys, private keys and Supabase `service_role` keys (anon keys are fine), plus `.env` values that `react-native-config` or `EXPO_PUBLIC_` compile into the bundle. Server-side folders are reported separately.
17
+ - **Is transport security off?** Cleartext HTTP or `debuggable` in the release Android manifest, `NSAllowsArbitraryLoads` on iOS.
18
+ - **Leftovers from old templates?** Flipper, JavaScriptCore instead of Hermes.
19
+
20
+ ```
21
+ ✖ CRITICAL React Native 0.77.1 is no longer supported
22
+ Latest is 0.87.1. You are 10 minor versions behind; only the latest 3 minors receive fixes.
23
+ ✖ CRITICAL New Architecture is disabled (android + ios)
24
+ The legacy architecture was removed in 0.82. This app cannot upgrade past 0.81 until it migrates.
25
+ ✖ CRITICAL AWS secret access key shipped inside the app (src/aws.js:4)
26
+ Value wJal…EY. Anyone who downloads the app can extract it. Revoke it now, then move the call to a server.
27
+ ▲ HIGH react-native-fast-image: no New Architecture support, unmaintained
28
+ Likely upgrade blocker. Alternatives: expo-image.
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```sh
34
+ npx nativekeel [path] # report
35
+ npx nativekeel plan --out UPGRADE.md # step-by-step upgrade plan
36
+ npx nativekeel --html report.html # shareable HTML report
37
+ npx nativekeel --markdown pr.md # summary for a pull request comment
38
+ npx nativekeel --sarif nk.sarif # GitHub code scanning
39
+ npx nativekeel --json # machine-readable output
40
+ npx nativekeel --fail-on high # exit 1 on high or critical (default: critical)
41
+ npx nativekeel --offline # no network at all, local checks only
42
+ npx nativekeel --verbose # list every outdated package
43
+ ```
44
+
45
+ Everything is free. The scan exits with code `1` when it finds a critical issue, so it can gate CI.
46
+
47
+ ### Upgrade plan
48
+
49
+ `nativekeel plan` turns the report into ordered work: stop leaks first, replace blocking packages, switch on the New Architecture on your current version, then upgrade React Native in small, reviewable hops with links to the Upgrade Helper diffs.
50
+
51
+ ### GitHub Action
52
+
53
+ ```yaml
54
+ # .github/workflows/nativekeel.yml
55
+ name: NativeKeel
56
+ on: [pull_request]
57
+ permissions:
58
+ contents: read
59
+ security-events: write # only needed with sarif: true
60
+ jobs:
61
+ health:
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v7
65
+ - uses: actions/setup-node@v7
66
+ with: { node-version: 20 }
67
+ - run: npm ci
68
+ - uses: AlicanAkyol/nativekeel@v0
69
+ with:
70
+ fail-on: critical
71
+ baseline: .nativekeel-baseline.json # optional
72
+ sarif: true # findings inline in the PR
73
+ ```
74
+
75
+ The report is added to the job summary. With a baseline, only findings that are new since the baseline fail the build:
76
+
77
+ ```sh
78
+ npx nativekeel --save-baseline .nativekeel-baseline.json # commit this file
79
+ ```
80
+
81
+ ### Reports for clients
82
+
83
+ Agencies can put their own name on the HTML report: `npx nativekeel plan --html plan.html --brand "Acme Mobile"`.
84
+
85
+ ## Why you can run this on private code
86
+
87
+ - **Your code never leaves your machine.** The only requests are package-name lookups on npm and React Native Directory. `--offline` makes none; a test traps `fetch` to prove it.
88
+ - **Zero dependencies, no install scripts.** Only Node.js built-ins.
89
+ - **Read-only.** It never changes your project.
90
+ - **Secrets are always masked** in every output.
91
+ - **Verifiable releases.** Published with npm provenance from GitHub Actions: `npm audit signatures`.
92
+
93
+ Details and vulnerability reporting: [SECURITY.md](SECURITY.md).
94
+
95
+ ## Need it done for you?
96
+
97
+ We do fixed-price React Native and Expo upgrades on a branch of your repository, delivered as a pull request with test builds. No calls needed. See https://nativekeel.com/#services.
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { loadProject } from '../src/project.js';
6
+ import { analyze, SEVERITIES } from '../src/analyze.js';
7
+ import { textReport } from '../src/report-text.js';
8
+ import { htmlReport } from '../src/report-html.js';
9
+ import { sarifReport } from '../src/report-sarif.js';
10
+ import { markdownReport } from '../src/report-md.js';
11
+ import { buildPlan, planToMarkdown } from '../src/plan.js';
12
+ import { applyBaseline, saveBaseline } from '../src/baseline.js';
13
+ import { compareScans, compareToMarkdown } from '../src/compare.js';
14
+ import { SITE_URL } from '../src/config.js';
15
+
16
+ const VALUE_FLAGS = new Set(['--html', '--out', '--baseline', '--save-baseline', '--brand', '--sarif', '--markdown', '--fail-on']);
17
+ const COMMANDS = new Set(['scan', 'plan', 'compare', 'help']);
18
+
19
+ function parseArgs(argv) {
20
+ const flags = {};
21
+ const positional = [];
22
+ for (let i = 0; i < argv.length; i++) {
23
+ const a = argv[i];
24
+ if (VALUE_FLAGS.has(a)) {
25
+ if (!argv[i + 1] || argv[i + 1].startsWith('--')) fail(`${a} needs a value`);
26
+ flags[a.slice(2)] = argv[++i];
27
+ } else if (a.startsWith('--') || a === '-h' || a === '-v') {
28
+ flags[a.replace(/^-+/, '')] = true;
29
+ } else {
30
+ positional.push(a);
31
+ }
32
+ }
33
+ const command = COMMANDS.has(positional[0]) ? positional.shift() : 'scan';
34
+ return { command, flags, positional };
35
+ }
36
+
37
+ function fail(message, code = 2) {
38
+ console.error(`nativekeel: ${message}`);
39
+ process.exit(code);
40
+ }
41
+
42
+ const HELP = `NativeKeel: health check for React Native and Expo apps
43
+
44
+ Usage
45
+ npx nativekeel [path] Scan an app and print the report
46
+ npx nativekeel plan [path] Write a step-by-step upgrade plan
47
+ npx nativekeel compare <before.json> <after.json>
48
+ What got fixed and what is new between two scans
49
+
50
+ Scan options
51
+ --json Machine-readable output
52
+ --html <file> Save a shareable HTML report
53
+ --markdown <file> Save a Markdown summary (for pull request comments)
54
+ --sarif <file> Save SARIF for GitHub code scanning
55
+ --verbose List every outdated package
56
+ --offline Make no network requests (local checks only)
57
+ --save-baseline <file> Accept current findings as known
58
+ --baseline <file> Only report findings not in the baseline (for CI)
59
+ --brand <name> Put your company name on the HTML report
60
+ --fail-on <level> Exit 1 at this severity or above: critical (default),
61
+ high, medium, low, or never
62
+
63
+ Plan options
64
+ --out <file> Save the plan as Markdown (default: print it)
65
+ --html <file> Save report + plan as one HTML file
66
+
67
+ The scan exits with code 1 when a critical issue is found (see --fail-on), so it
68
+ can gate CI.
69
+
70
+ Privacy: your code never leaves your machine. Without --offline, the only
71
+ requests are package-name lookups on registry.npmjs.org and
72
+ reactnative.directory. Secret values are always masked. ${SITE_URL}`;
73
+
74
+ async function runScan(root, flags) {
75
+ let project;
76
+ try {
77
+ project = loadProject(root);
78
+ } catch (e) {
79
+ fail(e.message);
80
+ }
81
+ if (!flags.json) process.stderr.write(`Scanning ${root}${flags.offline ? ' (offline)' : ''} …\n`);
82
+ return analyze(project, flags.offline ? { get: async () => null, offline: true } : {});
83
+ }
84
+
85
+ function packageVersion() {
86
+ return JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')).version;
87
+ }
88
+
89
+ function write(file, content) {
90
+ fs.writeFileSync(file, content);
91
+ process.stderr.write(`Saved ${path.resolve(file)}\n`);
92
+ }
93
+
94
+ async function main() {
95
+ const { command, flags, positional } = parseArgs(process.argv.slice(2));
96
+
97
+ if (flags.v || flags.version) {
98
+ console.log(packageVersion());
99
+ return 0;
100
+ }
101
+ if (command === 'help' || flags.h || flags.help) {
102
+ console.log(HELP);
103
+ return 0;
104
+ }
105
+
106
+ if (command === 'compare') {
107
+ if (positional.length !== 2) fail('usage: nativekeel compare <before.json> <after.json> (scans saved with --json)');
108
+ const [before, after] = positional.map((f) => {
109
+ try {
110
+ return JSON.parse(fs.readFileSync(f, 'utf8'));
111
+ } catch (e) {
112
+ return fail(`cannot read ${f}: ${e.message}`);
113
+ }
114
+ });
115
+ const md = compareToMarkdown(compareScans(before, after));
116
+ if (flags.out) write(flags.out, md);
117
+ else console.log(md);
118
+ return 0;
119
+ }
120
+
121
+ const root = path.resolve(positional[0] || '.');
122
+ const brand = flags.brand || null;
123
+
124
+ if (command === 'plan') {
125
+ const result = await runScan(root, flags);
126
+ const plan = buildPlan(result);
127
+ if (flags.html) write(flags.html, htmlReport(result, { plan, brand }));
128
+ const md = planToMarkdown(result, plan);
129
+ if (flags.out) write(flags.out, md);
130
+ else if (!flags.html) console.log(md);
131
+ return 0;
132
+ }
133
+
134
+ const failOn = flags['fail-on'] || 'critical';
135
+ if (!['critical', 'high', 'medium', 'low', 'never'].includes(failOn)) fail('--fail-on must be one of critical, high, medium, low, never');
136
+ let result = await runScan(root, flags);
137
+ if (flags['save-baseline']) {
138
+ const count = saveBaseline(flags['save-baseline'], result);
139
+ process.stderr.write(`Baseline saved with ${count} known findings: ${path.resolve(flags['save-baseline'])}\n`);
140
+ }
141
+ if (flags.baseline) {
142
+ try {
143
+ result = applyBaseline(flags.baseline, result);
144
+ } catch (e) {
145
+ fail(`cannot read baseline ${flags.baseline}: ${e.message}`);
146
+ }
147
+ }
148
+
149
+ if (flags.html) write(flags.html, htmlReport(result, { brand }));
150
+ if (flags.markdown) write(flags.markdown, markdownReport(result, { verbose: !!flags.verbose }));
151
+ if (flags.sarif) write(flags.sarif, `${JSON.stringify(sarifReport(result, { version: packageVersion() }), null, 2)}\n`);
152
+ if (flags.json) console.log(JSON.stringify(result, null, 2));
153
+ else console.log(textReport(result, { color: process.stdout.isTTY, verbose: !!flags.verbose }));
154
+ if (failOn === 'never') return 0;
155
+ const threshold = SEVERITIES.indexOf(failOn);
156
+ return result.findings.some((f) => SEVERITIES.indexOf(f.severity) <= threshold) ? 1 : 0;
157
+ }
158
+
159
+ main().then(
160
+ (code) => process.exit(code),
161
+ (e) => fail(e.stack || e.message),
162
+ );
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "nativekeel",
3
+ "version": "0.1.0",
4
+ "description": "Health check for React Native apps: version support, New Architecture blockers, unmaintained native modules and leaked secrets.",
5
+ "type": "module",
6
+ "bin": {
7
+ "nativekeel": "bin/nativekeel.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "keywords": [
17
+ "react-native",
18
+ "expo",
19
+ "upgrade",
20
+ "new-architecture",
21
+ "doctor",
22
+ "health-check",
23
+ "security"
24
+ ],
25
+ "license": "MIT",
26
+ "scripts": {
27
+ "test": "node --test test/*.test.js",
28
+ "scan": "node bin/nativekeel.js"
29
+ },
30
+ "homepage": "https://nativekeel.com",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/AlicanAkyol/nativekeel.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/AlicanAkyol/nativekeel/issues"
37
+ }
38
+ }