shipready 1.3.2 → 1.4.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/dist/checks/packageJson.js +11 -0
- package/dist/scanner.js +3 -1
- package/dist/utils/files.js +31 -8
- package/dist/utils/framework.js +141 -31
- package/dist/utils/report.js +4 -1
- package/package.json +1 -1
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import { isNodeEcosystem } from "../utils/framework.js";
|
|
1
2
|
const IMPORTANT_SCRIPTS = ["dev", "build", "test", "lint"];
|
|
2
3
|
/** Checks package.json existence and important scripts. */
|
|
3
4
|
export function checkPackageJson(project) {
|
|
4
5
|
const findings = [];
|
|
5
6
|
if (!project.hasPackageJson) {
|
|
7
|
+
// Static sites, Python, Go, Rust, etc. don't need a package.json -
|
|
8
|
+
// penalizing them for it would be unfair noise.
|
|
9
|
+
if (!isNodeEcosystem(project.framework)) {
|
|
10
|
+
findings.push({
|
|
11
|
+
severity: "info",
|
|
12
|
+
rule: "package-json.not-applicable",
|
|
13
|
+
message: `package.json not required for a ${project.framework} project`,
|
|
14
|
+
});
|
|
15
|
+
return { name: "package.json", findings };
|
|
16
|
+
}
|
|
6
17
|
findings.push({
|
|
7
18
|
severity: "error",
|
|
8
19
|
rule: "package-json.missing",
|
package/dist/scanner.js
CHANGED
|
@@ -14,6 +14,8 @@ import { isRuleDisabled, loadConfig } from "./config.js";
|
|
|
14
14
|
const CODE_ONLY = new Set([
|
|
15
15
|
".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts",
|
|
16
16
|
".vue", ".svelte", ".py", ".rb", ".go", ".rs", ".java", ".php",
|
|
17
|
+
// Inline <script> in static sites carries console.log/TODO noise too
|
|
18
|
+
".html", ".htm",
|
|
17
19
|
]);
|
|
18
20
|
/** Gathers structured project info from the given root. */
|
|
19
21
|
export async function detectProject(root, config) {
|
|
@@ -24,7 +26,7 @@ export async function detectProject(root, config) {
|
|
|
24
26
|
hasPackageJson: fileExists(root, "package.json"),
|
|
25
27
|
packageJson: pkg,
|
|
26
28
|
packageManager: detectPackageManager(root),
|
|
27
|
-
framework: detectFramework(pkg),
|
|
29
|
+
framework: detectFramework(pkg, root, sourceFiles),
|
|
28
30
|
scripts: pkg?.scripts ?? {},
|
|
29
31
|
sourceFiles,
|
|
30
32
|
};
|
package/dist/utils/files.js
CHANGED
|
@@ -3,14 +3,34 @@ import path from "node:path";
|
|
|
3
3
|
import fg from "fast-glob";
|
|
4
4
|
/** Glob patterns that are always ignored when scanning. */
|
|
5
5
|
export const IGNORE_PATTERNS = [
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
6
|
+
// Dependencies and VCS (at any depth)
|
|
7
|
+
"**/node_modules/**",
|
|
8
|
+
"**/.git/**",
|
|
9
|
+
// Build output (at any depth, e.g. mobile/build/)
|
|
10
|
+
"**/dist/**",
|
|
11
|
+
"**/build/**",
|
|
12
|
+
"**/.next/**",
|
|
13
|
+
"**/out/**",
|
|
14
|
+
"**/coverage/**",
|
|
15
|
+
"**/.cache/**",
|
|
16
|
+
"**/.turbo/**",
|
|
17
|
+
"**/.output/**",
|
|
18
|
+
// Minified/bundled vendor assets — not user code
|
|
19
|
+
"**/*.min.js",
|
|
20
|
+
"**/*.min.css",
|
|
21
|
+
"**/*.bundle.js",
|
|
22
|
+
"**/*.chunk.js",
|
|
23
|
+
"**/vendor/**",
|
|
24
|
+
"**/vendors/**",
|
|
25
|
+
// Python artifacts
|
|
26
|
+
"**/__pycache__/**",
|
|
27
|
+
"**/.venv/**",
|
|
28
|
+
"**/venv/**",
|
|
29
|
+
// Lockfiles and maps
|
|
30
|
+
"**/*.map",
|
|
31
|
+
"**/package-lock.json",
|
|
32
|
+
"**/pnpm-lock.yaml",
|
|
33
|
+
"**/yarn.lock",
|
|
14
34
|
];
|
|
15
35
|
/** Extensions we treat as scannable text/code files. */
|
|
16
36
|
export const CODE_EXTENSIONS = [
|
|
@@ -36,6 +56,9 @@ export const CODE_EXTENSIONS = [
|
|
|
36
56
|
"toml",
|
|
37
57
|
"env",
|
|
38
58
|
"sh",
|
|
59
|
+
// Static sites: inline <script> blocks can leak keys just as easily
|
|
60
|
+
"html",
|
|
61
|
+
"htm",
|
|
39
62
|
];
|
|
40
63
|
/** Returns true if the file exists (relative to root or absolute). */
|
|
41
64
|
export function fileExists(root, rel) {
|
package/dist/utils/framework.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileExists } from "./files.js";
|
|
2
|
-
/** Detects the package manager based on lockfiles. */
|
|
2
|
+
/** Detects the package manager based on lockfiles and manifests. */
|
|
3
3
|
export function detectPackageManager(root) {
|
|
4
|
+
// Node ecosystems (lockfile wins over manifest)
|
|
4
5
|
if (fileExists(root, "pnpm-lock.yaml"))
|
|
5
6
|
return "pnpm";
|
|
6
7
|
if (fileExists(root, "yarn.lock"))
|
|
@@ -9,34 +10,131 @@ export function detectPackageManager(root) {
|
|
|
9
10
|
return "bun";
|
|
10
11
|
if (fileExists(root, "package-lock.json"))
|
|
11
12
|
return "npm";
|
|
13
|
+
if (fileExists(root, "deno.json") || fileExists(root, "deno.jsonc"))
|
|
14
|
+
return "deno";
|
|
15
|
+
// package.json without a lockfile is still an npm-family project
|
|
16
|
+
if (fileExists(root, "package.json"))
|
|
17
|
+
return "npm";
|
|
18
|
+
// Other ecosystems
|
|
19
|
+
if (fileExists(root, "uv.lock"))
|
|
20
|
+
return "uv";
|
|
21
|
+
if (fileExists(root, "poetry.lock"))
|
|
22
|
+
return "poetry";
|
|
23
|
+
if (fileExists(root, "requirements.txt") ||
|
|
24
|
+
fileExists(root, "pyproject.toml") ||
|
|
25
|
+
fileExists(root, "Pipfile")) {
|
|
26
|
+
return "pip";
|
|
27
|
+
}
|
|
28
|
+
if (fileExists(root, "Cargo.toml"))
|
|
29
|
+
return "cargo";
|
|
30
|
+
if (fileExists(root, "go.mod"))
|
|
31
|
+
return "go";
|
|
32
|
+
if (fileExists(root, "composer.json"))
|
|
33
|
+
return "composer";
|
|
34
|
+
if (fileExists(root, "Gemfile"))
|
|
35
|
+
return "bundler";
|
|
36
|
+
return "none";
|
|
37
|
+
}
|
|
38
|
+
/** Counts source files matching any of the given extensions. */
|
|
39
|
+
function countByExt(files, exts) {
|
|
40
|
+
return files.filter((f) => exts.some((e) => f.toLowerCase().endsWith(e))).length;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Detects the framework/project type. Checks package.json dependencies
|
|
44
|
+
* first, then falls back to manifest files and source file extensions so
|
|
45
|
+
* that static sites, Python, Go, Rust, PHP, and Ruby projects are
|
|
46
|
+
* identified instead of reported as "unknown".
|
|
47
|
+
*/
|
|
48
|
+
export function detectFramework(pkg, root, sourceFiles = []) {
|
|
49
|
+
if (pkg) {
|
|
50
|
+
const deps = {
|
|
51
|
+
...pkg.dependencies,
|
|
52
|
+
...pkg.devDependencies,
|
|
53
|
+
};
|
|
54
|
+
// Order matters: meta-frameworks before their underlying libraries.
|
|
55
|
+
if (deps["next"])
|
|
56
|
+
return "Next.js";
|
|
57
|
+
if (deps["astro"])
|
|
58
|
+
return "Astro";
|
|
59
|
+
if (deps["@remix-run/react"] || deps["@remix-run/node"])
|
|
60
|
+
return "Remix";
|
|
61
|
+
if (deps["@angular/core"])
|
|
62
|
+
return "Angular";
|
|
63
|
+
if (deps["gatsby"])
|
|
64
|
+
return "Gatsby";
|
|
65
|
+
if (deps["@nestjs/core"])
|
|
66
|
+
return "NestJS";
|
|
67
|
+
if (deps["nuxt"] || deps["vue"])
|
|
68
|
+
return "Vue";
|
|
69
|
+
if (deps["svelte"] || deps["@sveltejs/kit"])
|
|
70
|
+
return "Svelte";
|
|
71
|
+
if (deps["vite"])
|
|
72
|
+
return "Vite";
|
|
73
|
+
if (deps["react"])
|
|
74
|
+
return "React";
|
|
75
|
+
if (deps["express"])
|
|
76
|
+
return "Express";
|
|
77
|
+
return "Node.js";
|
|
78
|
+
}
|
|
79
|
+
// No package.json: detect by manifests, then by dominant file type.
|
|
80
|
+
if (root) {
|
|
81
|
+
if (fileExists(root, "deno.json") || fileExists(root, "deno.jsonc"))
|
|
82
|
+
return "Deno";
|
|
83
|
+
if (fileExists(root, "pyproject.toml") ||
|
|
84
|
+
fileExists(root, "requirements.txt") ||
|
|
85
|
+
fileExists(root, "Pipfile")) {
|
|
86
|
+
return "Python";
|
|
87
|
+
}
|
|
88
|
+
if (fileExists(root, "Cargo.toml"))
|
|
89
|
+
return "Rust";
|
|
90
|
+
if (fileExists(root, "go.mod"))
|
|
91
|
+
return "Go";
|
|
92
|
+
if (fileExists(root, "composer.json"))
|
|
93
|
+
return "PHP";
|
|
94
|
+
if (fileExists(root, "Gemfile"))
|
|
95
|
+
return "Ruby";
|
|
96
|
+
if (fileExists(root, "pom.xml") || fileExists(root, "build.gradle") || fileExists(root, "build.gradle.kts")) {
|
|
97
|
+
return "Java";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const html = countByExt(sourceFiles, [".html", ".htm"]);
|
|
101
|
+
const py = countByExt(sourceFiles, [".py"]);
|
|
102
|
+
const go = countByExt(sourceFiles, [".go"]);
|
|
103
|
+
const rs = countByExt(sourceFiles, [".rs"]);
|
|
104
|
+
const php = countByExt(sourceFiles, [".php"]);
|
|
105
|
+
const rb = countByExt(sourceFiles, [".rb"]);
|
|
106
|
+
const js = countByExt(sourceFiles, [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"]);
|
|
107
|
+
const counts = [
|
|
108
|
+
["Python", py],
|
|
109
|
+
["Go", go],
|
|
110
|
+
["Rust", rs],
|
|
111
|
+
["PHP", php],
|
|
112
|
+
["Ruby", rb],
|
|
113
|
+
];
|
|
114
|
+
const [lang, max] = counts.reduce((a, b) => (b[1] > a[1] ? b : a), ["unknown", 0]);
|
|
115
|
+
// A language wins only when it clearly dominates over HTML/JS glue files.
|
|
116
|
+
if (max > 0 && max >= html && max >= js)
|
|
117
|
+
return lang;
|
|
118
|
+
// HTML pages with (or without) plain JS assets: a static site.
|
|
119
|
+
if (html > 0)
|
|
120
|
+
return "Static HTML";
|
|
121
|
+
if (js > 0)
|
|
122
|
+
return "Node.js";
|
|
12
123
|
return "unknown";
|
|
13
124
|
}
|
|
14
|
-
/**
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (deps["nuxt"] || deps["vue"])
|
|
28
|
-
return "Vue";
|
|
29
|
-
if (deps["svelte"] || deps["@sveltejs/kit"])
|
|
30
|
-
return "Svelte";
|
|
31
|
-
if (deps["vite"] && deps["react"])
|
|
32
|
-
return "Vite";
|
|
33
|
-
if (deps["vite"])
|
|
34
|
-
return "Vite";
|
|
35
|
-
if (deps["react"])
|
|
36
|
-
return "React";
|
|
37
|
-
if (deps["express"])
|
|
38
|
-
return "Express";
|
|
39
|
-
return "Node.js";
|
|
125
|
+
/** True when the project belongs to the npm/Node ecosystem. */
|
|
126
|
+
export function isNodeEcosystem(framework) {
|
|
127
|
+
return ![
|
|
128
|
+
"Static HTML",
|
|
129
|
+
"Python",
|
|
130
|
+
"Go",
|
|
131
|
+
"Rust",
|
|
132
|
+
"PHP",
|
|
133
|
+
"Ruby",
|
|
134
|
+
"Java",
|
|
135
|
+
"Deno",
|
|
136
|
+
"unknown",
|
|
137
|
+
].includes(framework);
|
|
40
138
|
}
|
|
41
139
|
/** Returns the install command for a given package manager. */
|
|
42
140
|
export function installCommand(pm) {
|
|
@@ -47,8 +145,21 @@ export function installCommand(pm) {
|
|
|
47
145
|
return "yarn install";
|
|
48
146
|
case "bun":
|
|
49
147
|
return "bun install";
|
|
50
|
-
case "
|
|
51
|
-
|
|
148
|
+
case "poetry":
|
|
149
|
+
return "poetry install";
|
|
150
|
+
case "uv":
|
|
151
|
+
return "uv sync";
|
|
152
|
+
case "pip":
|
|
153
|
+
return "pip install -r requirements.txt";
|
|
154
|
+
case "cargo":
|
|
155
|
+
return "cargo build";
|
|
156
|
+
case "go":
|
|
157
|
+
return "go mod download";
|
|
158
|
+
case "composer":
|
|
159
|
+
return "composer install";
|
|
160
|
+
case "bundler":
|
|
161
|
+
return "bundle install";
|
|
162
|
+
default:
|
|
52
163
|
return "npm install";
|
|
53
164
|
}
|
|
54
165
|
}
|
|
@@ -61,8 +172,7 @@ export function runCommand(pm, script) {
|
|
|
61
172
|
return `yarn ${script}`;
|
|
62
173
|
case "bun":
|
|
63
174
|
return `bun run ${script}`;
|
|
64
|
-
|
|
65
|
-
case "unknown":
|
|
175
|
+
default:
|
|
66
176
|
return `npm run ${script}`;
|
|
67
177
|
}
|
|
68
178
|
}
|
package/dist/utils/report.js
CHANGED
|
@@ -120,11 +120,14 @@ export function renderReport(report, verbose = false, version) {
|
|
|
120
120
|
const lines = [];
|
|
121
121
|
const { project } = report;
|
|
122
122
|
const title = pc.bold(pc.magenta("shipready")) + (version ? pc.dim(` v${version}`) : "");
|
|
123
|
+
const pmLabel = project.packageManager === "none" || project.packageManager === "unknown"
|
|
124
|
+
? pc.dim("\u2014")
|
|
125
|
+
: project.packageManager;
|
|
123
126
|
const meta = pc.dim("project ") +
|
|
124
127
|
project.framework +
|
|
125
128
|
pc.dim(" \u00b7 ") +
|
|
126
129
|
pc.dim("pm ") +
|
|
127
|
-
|
|
130
|
+
pmLabel;
|
|
128
131
|
lines.push("");
|
|
129
132
|
lines.push(...box([title, meta]));
|
|
130
133
|
lines.push("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipready",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Pre-flight check for your repo before shipping. Scans AI-coded projects for secrets, env issues, debug leftovers, and generates AI-agent instruction files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|