@lpm-registry/cli 0.2.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/CHANGELOG.md +36 -0
- package/LICENSE +15 -0
- package/README.md +406 -0
- package/bin/lpm.js +334 -0
- package/index.d.ts +131 -0
- package/index.js +31 -0
- package/lib/api.js +324 -0
- package/lib/commands/add.js +1217 -0
- package/lib/commands/audit.js +283 -0
- package/lib/commands/cache.js +209 -0
- package/lib/commands/check-name.js +112 -0
- package/lib/commands/config.js +174 -0
- package/lib/commands/doctor.js +142 -0
- package/lib/commands/info.js +215 -0
- package/lib/commands/init.js +146 -0
- package/lib/commands/install.js +217 -0
- package/lib/commands/login.js +547 -0
- package/lib/commands/logout.js +94 -0
- package/lib/commands/marketplace-compare.js +164 -0
- package/lib/commands/marketplace-earnings.js +89 -0
- package/lib/commands/mcp-setup.js +363 -0
- package/lib/commands/open.js +82 -0
- package/lib/commands/outdated.js +291 -0
- package/lib/commands/pool-stats.js +100 -0
- package/lib/commands/publish.js +707 -0
- package/lib/commands/quality.js +211 -0
- package/lib/commands/remove.js +82 -0
- package/lib/commands/run.js +14 -0
- package/lib/commands/search.js +143 -0
- package/lib/commands/setup.js +92 -0
- package/lib/commands/skills.js +863 -0
- package/lib/commands/token-rotate.js +25 -0
- package/lib/commands/whoami.js +129 -0
- package/lib/config.js +240 -0
- package/lib/constants.js +190 -0
- package/lib/ecosystem.js +501 -0
- package/lib/editors.js +215 -0
- package/lib/import-rewriter.js +364 -0
- package/lib/install-targets/mcp-server.js +245 -0
- package/lib/install-targets/vscode-extension.js +178 -0
- package/lib/install-targets.js +82 -0
- package/lib/integrity.js +179 -0
- package/lib/lpm-config-prompts.js +102 -0
- package/lib/lpm-config.js +408 -0
- package/lib/project-utils.js +152 -0
- package/lib/quality/checks.js +654 -0
- package/lib/quality/display.js +139 -0
- package/lib/quality/score.js +115 -0
- package/lib/quality/swift-checks.js +447 -0
- package/lib/safe-path.js +180 -0
- package/lib/secure-store.js +288 -0
- package/lib/swift-project.js +637 -0
- package/lib/ui.js +40 -0
- package/package.json +74 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import chalk from "chalk"
|
|
2
|
+
|
|
3
|
+
const TIER_COLORS = {
|
|
4
|
+
excellent: chalk.green,
|
|
5
|
+
good: chalk.blue,
|
|
6
|
+
fair: chalk.yellow,
|
|
7
|
+
"needs-work": chalk.gray,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TIER_LABELS = {
|
|
11
|
+
excellent: "Excellent",
|
|
12
|
+
good: "Good",
|
|
13
|
+
fair: "Fair",
|
|
14
|
+
"needs-work": "Needs Work",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CATEGORY_LABELS = {
|
|
18
|
+
documentation: "Documentation",
|
|
19
|
+
code: "Code Quality",
|
|
20
|
+
testing: "Testing",
|
|
21
|
+
health: "Package Health",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Render a progress bar string
|
|
26
|
+
* @param {number} value
|
|
27
|
+
* @param {number} max
|
|
28
|
+
* @param {number} width
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function progressBar(value, max, width = 18) {
|
|
32
|
+
const ratio = Math.min(value / max, 1)
|
|
33
|
+
const filled = Math.round(ratio * width)
|
|
34
|
+
const empty = width - filled
|
|
35
|
+
return chalk.cyan("█".repeat(filled)) + chalk.gray("░".repeat(empty))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Display quality report in the terminal.
|
|
40
|
+
*
|
|
41
|
+
* @param {{ score: number, checks: Array, meta: object }} result
|
|
42
|
+
*/
|
|
43
|
+
export function displayQualityReport({ score, checks, meta }) {
|
|
44
|
+
const tierColor = TIER_COLORS[meta.tier] || chalk.white
|
|
45
|
+
const tierLabel = TIER_LABELS[meta.tier] || meta.tier
|
|
46
|
+
|
|
47
|
+
const hasServerOnly = checks.some(c => c.serverOnly)
|
|
48
|
+
|
|
49
|
+
console.log("")
|
|
50
|
+
|
|
51
|
+
// Individual checks first
|
|
52
|
+
for (const check of checks) {
|
|
53
|
+
if (check.serverOnly) {
|
|
54
|
+
// Show server-only checks dimmed with ~ icon
|
|
55
|
+
const icon = chalk.dim("~")
|
|
56
|
+
const label = chalk.dim(check.label)
|
|
57
|
+
const detail = chalk.dim(" (verified after publish)")
|
|
58
|
+
console.log(` ${icon} ${label}${detail}`)
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
const icon = check.passed ? chalk.green("✓") : chalk.red("✗")
|
|
62
|
+
const label = check.passed ? check.label : chalk.dim(check.label)
|
|
63
|
+
const detail = check.detail ? chalk.dim(` (${check.detail})`) : ""
|
|
64
|
+
console.log(` ${icon} ${label}${detail}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Tips for failed checks (exclude server-only)
|
|
68
|
+
const failed = checks.filter(c => !c.passed && !c.serverOnly)
|
|
69
|
+
if (failed.length > 0) {
|
|
70
|
+
console.log("")
|
|
71
|
+
const tips = failed.slice(0, 3)
|
|
72
|
+
for (const check of tips) {
|
|
73
|
+
console.log(
|
|
74
|
+
chalk.dim(` Tip: ${getTip(check.id)} (+${check.maxPoints} points)`),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Score summary at the end
|
|
80
|
+
console.log("")
|
|
81
|
+
console.log(
|
|
82
|
+
` ${chalk.bold("Quality Score:")} ${tierColor(`${score}/100`)} ${tierColor(`(${tierLabel})`)}${hasServerOnly ? chalk.dim(" — estimated, final score after publish") : ""}`,
|
|
83
|
+
)
|
|
84
|
+
console.log("")
|
|
85
|
+
|
|
86
|
+
// Category bars
|
|
87
|
+
for (const [cat, { score: catScore, max }] of Object.entries(
|
|
88
|
+
meta.categories,
|
|
89
|
+
)) {
|
|
90
|
+
const label = (CATEGORY_LABELS[cat] || cat).padEnd(16)
|
|
91
|
+
const bar = progressBar(catScore, max)
|
|
92
|
+
console.log(` ${label}${bar} ${catScore}/${max}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log("")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get a human-readable tip for a failed check
|
|
100
|
+
* @param {string} checkId
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
function getTip(checkId) {
|
|
104
|
+
const tips = {
|
|
105
|
+
"has-readme": "Add a README.md with at least 100 characters",
|
|
106
|
+
"readme-install": "Add an install/getting started section to your README",
|
|
107
|
+
"readme-usage": "Add usage examples with code blocks to your README",
|
|
108
|
+
"readme-api": "Add an API/reference section to your README",
|
|
109
|
+
"has-changelog": "Add a CHANGELOG.md file",
|
|
110
|
+
"has-license": "Add a LICENSE file",
|
|
111
|
+
"has-types": 'Add TypeScript types ("types" field or .d.ts files)',
|
|
112
|
+
"intellisense-coverage":
|
|
113
|
+
"Add .d.ts type definitions or JSDoc @param/@returns comments",
|
|
114
|
+
"esm-exports": 'Add "type": "module" or "exports" to package.json',
|
|
115
|
+
"tree-shakable":
|
|
116
|
+
'Add "sideEffects": false to package.json for tree-shaking',
|
|
117
|
+
"no-eval": "Remove eval() and new Function() usage",
|
|
118
|
+
"has-engines": 'Add "engines": { "node": ">=18" } to package.json',
|
|
119
|
+
"has-exports-map": 'Add an "exports" map to package.json',
|
|
120
|
+
"small-deps": "Reduce the number of production dependencies",
|
|
121
|
+
"source-maps": "Include .js.map source maps for easier debugging",
|
|
122
|
+
"has-test-files": "Add test files (*.test.js, *.spec.js)",
|
|
123
|
+
"has-test-script": "Add a test script to package.json",
|
|
124
|
+
"has-description": "Add a description (>10 chars) to package.json",
|
|
125
|
+
"has-keywords": "Add keywords to package.json",
|
|
126
|
+
"has-repository": "Add a repository field to package.json",
|
|
127
|
+
"has-homepage": "Add a homepage field to package.json",
|
|
128
|
+
"reasonable-size": "Reduce unpacked size (check for unnecessary files)",
|
|
129
|
+
"no-vulnerabilities": "Fix known vulnerabilities in dependencies",
|
|
130
|
+
"maintenance-health": "Publish updates regularly (within 90 days)",
|
|
131
|
+
"semver-consistency": "Use valid semantic versioning (major.minor.patch)",
|
|
132
|
+
"author-verified": "Link your GitHub or LinkedIn in your profile settings",
|
|
133
|
+
"has-skills":
|
|
134
|
+
"Add Agent Skills in .lpm/skills/*.md to help AI assistants use your package",
|
|
135
|
+
"skills-comprehensive":
|
|
136
|
+
"Add 3+ Agent Skills covering getting-started, best-practices, and anti-patterns",
|
|
137
|
+
}
|
|
138
|
+
return tips[checkId] || `Improve: ${checkId}`
|
|
139
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { getSourcePackageInfo, checks as jsChecks } from "./checks.js"
|
|
2
|
+
import { swiftChecks, xcframeworkChecks } from "./swift-checks.js"
|
|
3
|
+
|
|
4
|
+
const TIERS = [
|
|
5
|
+
{ min: 90, tier: "excellent" },
|
|
6
|
+
{ min: 70, tier: "good" },
|
|
7
|
+
{ min: 50, tier: "fair" },
|
|
8
|
+
{ min: 0, tier: "needs-work" },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
function getTier(score) {
|
|
12
|
+
return TIERS.find(t => score >= t.min)?.tier || "needs-work"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the check set for a given ecosystem.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} ecosystem - "js", "swift", "rust", etc.
|
|
19
|
+
* @returns {Array} Check definitions
|
|
20
|
+
*/
|
|
21
|
+
function getChecksForEcosystem(ecosystem) {
|
|
22
|
+
switch (ecosystem) {
|
|
23
|
+
case "swift":
|
|
24
|
+
return swiftChecks
|
|
25
|
+
case "xcframework":
|
|
26
|
+
return xcframeworkChecks
|
|
27
|
+
default:
|
|
28
|
+
return jsChecks
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run all quality checks and compute the score.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} params
|
|
36
|
+
* @param {object} params.packageJson - Parsed package.json
|
|
37
|
+
* @param {string|null} params.readme - README content
|
|
38
|
+
* @param {object|null} params.lpmConfig - Parsed lpm.config.json
|
|
39
|
+
* @param {Array} params.files - File list (each has .path)
|
|
40
|
+
* @param {number} [params.unpackedSize] - Unpacked size in bytes
|
|
41
|
+
* @param {string} [params.ecosystem] - Ecosystem identifier (default: "js")
|
|
42
|
+
* @param {object} [params.swiftManifest] - Parsed Swift manifest metadata (for Swift packages)
|
|
43
|
+
* @returns {{ score: number, checks: Array, meta: object }}
|
|
44
|
+
*/
|
|
45
|
+
export function runQualityChecks({
|
|
46
|
+
packageJson,
|
|
47
|
+
readme,
|
|
48
|
+
lpmConfig,
|
|
49
|
+
files,
|
|
50
|
+
unpackedSize,
|
|
51
|
+
ecosystem = "js",
|
|
52
|
+
swiftManifest,
|
|
53
|
+
xcframeworkMeta,
|
|
54
|
+
}) {
|
|
55
|
+
const context = {
|
|
56
|
+
packageJson,
|
|
57
|
+
readme,
|
|
58
|
+
lpmConfig,
|
|
59
|
+
files,
|
|
60
|
+
unpackedSize,
|
|
61
|
+
swiftManifest,
|
|
62
|
+
xcframeworkMeta,
|
|
63
|
+
}
|
|
64
|
+
const checks = getChecksForEcosystem(ecosystem)
|
|
65
|
+
|
|
66
|
+
const results = checks.map(check => {
|
|
67
|
+
const result = check.run(context)
|
|
68
|
+
// Some checks return custom points (e.g. smallDeps, reasonableSize)
|
|
69
|
+
const points = result.passed
|
|
70
|
+
? result.points !== undefined
|
|
71
|
+
? result.points
|
|
72
|
+
: check.maxPoints
|
|
73
|
+
: 0
|
|
74
|
+
return {
|
|
75
|
+
id: check.id,
|
|
76
|
+
category: check.category,
|
|
77
|
+
label: check.label,
|
|
78
|
+
passed: result.passed,
|
|
79
|
+
points,
|
|
80
|
+
maxPoints: check.maxPoints,
|
|
81
|
+
detail: result.detail,
|
|
82
|
+
...(result.serverOnly && { serverOnly: true }),
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Compute category scores dynamically from actual check maxPoints
|
|
87
|
+
const categories = {}
|
|
88
|
+
for (const result of results) {
|
|
89
|
+
if (!categories[result.category]) {
|
|
90
|
+
categories[result.category] = { score: 0, max: 0 }
|
|
91
|
+
}
|
|
92
|
+
categories[result.category].score += result.points
|
|
93
|
+
categories[result.category].max += result.maxPoints
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const totalScore = Object.values(categories).reduce(
|
|
97
|
+
(sum, c) => sum + c.score,
|
|
98
|
+
0,
|
|
99
|
+
)
|
|
100
|
+
const maxScore = Object.values(categories).reduce((sum, c) => sum + c.max, 0)
|
|
101
|
+
|
|
102
|
+
const meta = {
|
|
103
|
+
version: 1,
|
|
104
|
+
tier: getTier(totalScore),
|
|
105
|
+
score: totalScore,
|
|
106
|
+
maxScore,
|
|
107
|
+
ecosystem,
|
|
108
|
+
isSourcePackage: !!lpmConfig,
|
|
109
|
+
categories,
|
|
110
|
+
sourcePackageInfo: getSourcePackageInfo(lpmConfig),
|
|
111
|
+
computedAt: new Date().toISOString(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { score: totalScore, checks: results, meta }
|
|
115
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift-specific quality check definitions for LPM packages.
|
|
3
|
+
* Replaces JS-specific checks (TypeScript types, ESM, tree-shaking, etc.)
|
|
4
|
+
* while keeping universal checks (README, license, testing, health).
|
|
5
|
+
*
|
|
6
|
+
* Categories:
|
|
7
|
+
* - documentation: 22 points (6 checks)
|
|
8
|
+
* - code: 31 points (6 checks)
|
|
9
|
+
* - testing: 11 points (2 checks)
|
|
10
|
+
* - health: 36 points (11 checks)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// --- Documentation checks (22 points) ---
|
|
14
|
+
// Reused from JS: hasReadme, readmeHasInstall, readmeHasUsage, readmeHasApi, hasChangelog, hasLicense
|
|
15
|
+
// readmeHasInstall is modified to also look for Swift-specific install patterns
|
|
16
|
+
|
|
17
|
+
export const swiftReadmeHasInstall = {
|
|
18
|
+
id: "readme-install",
|
|
19
|
+
category: "documentation",
|
|
20
|
+
label: "README has install section",
|
|
21
|
+
maxPoints: 3,
|
|
22
|
+
run: ({ readme }) => {
|
|
23
|
+
if (!readme) return { passed: false, detail: "No README" }
|
|
24
|
+
const lower = readme.toLowerCase()
|
|
25
|
+
const hasSection =
|
|
26
|
+
lower.includes("## install") ||
|
|
27
|
+
lower.includes("## getting started") ||
|
|
28
|
+
lower.includes("## setup") ||
|
|
29
|
+
lower.includes("## requirements") ||
|
|
30
|
+
lower.includes("swift package manager") ||
|
|
31
|
+
lower.includes("package.swift") ||
|
|
32
|
+
lower.includes(".package(") ||
|
|
33
|
+
lower.includes("lpm add") ||
|
|
34
|
+
lower.includes("lpm install")
|
|
35
|
+
return {
|
|
36
|
+
passed: hasSection,
|
|
37
|
+
detail: hasSection
|
|
38
|
+
? "Install instructions found"
|
|
39
|
+
: "No install section found in README",
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- Code Quality checks (31 points) — Swift-specific ---
|
|
45
|
+
|
|
46
|
+
export const hasPlatformDeclarations = {
|
|
47
|
+
id: "has-platforms",
|
|
48
|
+
category: "code",
|
|
49
|
+
label: "Has platform declarations",
|
|
50
|
+
maxPoints: 6,
|
|
51
|
+
run: ({ swiftManifest }) => {
|
|
52
|
+
if (!swiftManifest)
|
|
53
|
+
return { passed: false, detail: "No Swift manifest data" }
|
|
54
|
+
const platforms = swiftManifest.platforms || []
|
|
55
|
+
if (platforms.length === 0) {
|
|
56
|
+
return {
|
|
57
|
+
passed: false,
|
|
58
|
+
detail:
|
|
59
|
+
"No platform declarations in Package.swift (add platforms: [.iOS(.v16), .macOS(.v13)])",
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const names = platforms.map(p => `${p.name} ${p.version}`).join(", ")
|
|
63
|
+
return {
|
|
64
|
+
passed: true,
|
|
65
|
+
detail: `Platforms: ${names}`,
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const recentToolsVersion = {
|
|
71
|
+
id: "recent-tools-version",
|
|
72
|
+
category: "code",
|
|
73
|
+
label: "Uses recent swift-tools-version",
|
|
74
|
+
maxPoints: 5,
|
|
75
|
+
run: ({ swiftManifest }) => {
|
|
76
|
+
if (!swiftManifest)
|
|
77
|
+
return { passed: false, detail: "No Swift manifest data" }
|
|
78
|
+
const version = swiftManifest.toolsVersion
|
|
79
|
+
if (!version) {
|
|
80
|
+
return { passed: false, detail: "No swift-tools-version found" }
|
|
81
|
+
}
|
|
82
|
+
// Parse major.minor from version string
|
|
83
|
+
const match = version.match(/^(\d+)\.(\d+)/)
|
|
84
|
+
if (!match) {
|
|
85
|
+
return { passed: false, detail: `Invalid tools version: ${version}` }
|
|
86
|
+
}
|
|
87
|
+
const major = parseInt(match[1], 10)
|
|
88
|
+
const minor = parseInt(match[2], 10)
|
|
89
|
+
// 5.9+ is recent (Swift 5.9 introduced macros, 5.10 strict concurrency)
|
|
90
|
+
const isRecent = major > 5 || (major === 5 && minor >= 9)
|
|
91
|
+
return {
|
|
92
|
+
passed: isRecent,
|
|
93
|
+
detail: isRecent
|
|
94
|
+
? `swift-tools-version: ${version}`
|
|
95
|
+
: `swift-tools-version ${version} is outdated (5.9+ recommended)`,
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const supportsMultiplePlatforms = {
|
|
101
|
+
id: "multi-platform",
|
|
102
|
+
category: "code",
|
|
103
|
+
label: "Supports multiple platforms",
|
|
104
|
+
maxPoints: 4,
|
|
105
|
+
run: ({ swiftManifest }) => {
|
|
106
|
+
if (!swiftManifest)
|
|
107
|
+
return { passed: false, detail: "No Swift manifest data" }
|
|
108
|
+
const platforms = swiftManifest.platforms || []
|
|
109
|
+
const count = platforms.length
|
|
110
|
+
let points
|
|
111
|
+
if (count >= 3) points = 4
|
|
112
|
+
else if (count === 2) points = 3
|
|
113
|
+
else if (count === 1) points = 2
|
|
114
|
+
else points = 0
|
|
115
|
+
return {
|
|
116
|
+
passed: points > 0,
|
|
117
|
+
points,
|
|
118
|
+
detail:
|
|
119
|
+
count > 0
|
|
120
|
+
? `Supports ${count} platform${count !== 1 ? "s" : ""}`
|
|
121
|
+
: "No platform declarations",
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const hasPublicAPI = {
|
|
127
|
+
id: "has-public-api",
|
|
128
|
+
category: "code",
|
|
129
|
+
label: "Has public API surface",
|
|
130
|
+
maxPoints: 5,
|
|
131
|
+
run: ({ files }) => {
|
|
132
|
+
// Check if any .swift source files exist (exclude manifest and tests)
|
|
133
|
+
const swiftFiles = files.filter(f => {
|
|
134
|
+
const name = f.path || f
|
|
135
|
+
return (
|
|
136
|
+
name.endsWith(".swift") &&
|
|
137
|
+
!name.includes("Tests/") &&
|
|
138
|
+
name !== "Package.swift"
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
if (swiftFiles.length === 0) {
|
|
142
|
+
return { passed: false, detail: "No Swift source files found" }
|
|
143
|
+
}
|
|
144
|
+
// CLI can't read file contents from the file list alone
|
|
145
|
+
// Server will do deeper analysis; CLI just checks file presence
|
|
146
|
+
return {
|
|
147
|
+
passed: true,
|
|
148
|
+
detail: `${swiftFiles.length} Swift source file${swiftFiles.length !== 1 ? "s" : ""} found`,
|
|
149
|
+
serverOnly: true,
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const hasDocComments = {
|
|
155
|
+
id: "has-doc-comments",
|
|
156
|
+
category: "code",
|
|
157
|
+
label: "Has DocC documentation",
|
|
158
|
+
maxPoints: 7,
|
|
159
|
+
run: () => {
|
|
160
|
+
// Full check runs server-side from tarball contents.
|
|
161
|
+
// CLI assumes pass; server overrides if no documentation found.
|
|
162
|
+
return {
|
|
163
|
+
passed: true,
|
|
164
|
+
detail: "Full check runs server-side",
|
|
165
|
+
serverOnly: true,
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const smallDepsSwift = {
|
|
171
|
+
id: "small-deps",
|
|
172
|
+
category: "code",
|
|
173
|
+
label: "Small dependency footprint",
|
|
174
|
+
maxPoints: 4,
|
|
175
|
+
run: ({ swiftManifest }) => {
|
|
176
|
+
if (!swiftManifest)
|
|
177
|
+
return { passed: true, points: 4, detail: "No manifest data" }
|
|
178
|
+
const deps = swiftManifest.dependencies?.length || 0
|
|
179
|
+
let points
|
|
180
|
+
if (deps === 0) points = 4
|
|
181
|
+
else if (deps <= 2) points = 3
|
|
182
|
+
else if (deps <= 5) points = 2
|
|
183
|
+
else if (deps <= 10) points = 1
|
|
184
|
+
else points = 0
|
|
185
|
+
return {
|
|
186
|
+
passed: points > 0,
|
|
187
|
+
points,
|
|
188
|
+
detail: `${deps} ${deps === 1 ? "dependency" : "dependencies"}`,
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- Testing checks (15 points) ---
|
|
194
|
+
|
|
195
|
+
export const swiftHasTestTargets = {
|
|
196
|
+
id: "has-test-files",
|
|
197
|
+
category: "testing",
|
|
198
|
+
label: "Has test targets",
|
|
199
|
+
maxPoints: 7,
|
|
200
|
+
run: ({ swiftManifest, files }) => {
|
|
201
|
+
// Check Swift manifest for test targets
|
|
202
|
+
const testTargets = (swiftManifest?.targets || []).filter(
|
|
203
|
+
t => t.type === "test",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
// Also check file paths
|
|
207
|
+
const testFiles = files.filter(f => {
|
|
208
|
+
const name = (f.path || f).toLowerCase()
|
|
209
|
+
return name.includes("tests/") || name.includes("test/")
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const passed = testTargets.length > 0 || testFiles.length > 0
|
|
213
|
+
let detail
|
|
214
|
+
if (testTargets.length > 0) {
|
|
215
|
+
detail = `${testTargets.length} test target${testTargets.length !== 1 ? "s" : ""}: ${testTargets.map(t => t.name).join(", ")}`
|
|
216
|
+
} else if (testFiles.length > 0) {
|
|
217
|
+
detail = `${testFiles.length} test file${testFiles.length !== 1 ? "s" : ""} found`
|
|
218
|
+
} else {
|
|
219
|
+
detail = "No test targets or test files found"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { passed, detail }
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const swiftHasTestScript = {
|
|
227
|
+
id: "has-test-script",
|
|
228
|
+
category: "testing",
|
|
229
|
+
label: "Has test configuration",
|
|
230
|
+
maxPoints: 4,
|
|
231
|
+
run: ({ swiftManifest }) => {
|
|
232
|
+
// For Swift, having test targets in the manifest is the equivalent
|
|
233
|
+
const testTargets = (swiftManifest?.targets || []).filter(
|
|
234
|
+
t => t.type === "test",
|
|
235
|
+
)
|
|
236
|
+
const passed = testTargets.length > 0
|
|
237
|
+
return {
|
|
238
|
+
passed,
|
|
239
|
+
detail: passed
|
|
240
|
+
? "Test targets defined in Package.swift"
|
|
241
|
+
: "No test targets in Package.swift",
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// --- XCFramework-specific checks ---
|
|
247
|
+
|
|
248
|
+
export const xcfHasValidPlist = {
|
|
249
|
+
id: "xcf-valid-plist",
|
|
250
|
+
category: "code",
|
|
251
|
+
label: "Valid Info.plist",
|
|
252
|
+
maxPoints: 10,
|
|
253
|
+
run: ({ xcframeworkMeta }) => {
|
|
254
|
+
if (!xcframeworkMeta)
|
|
255
|
+
return { passed: false, detail: "No XCFramework metadata" }
|
|
256
|
+
const slices = xcframeworkMeta.slices || []
|
|
257
|
+
if (slices.length === 0)
|
|
258
|
+
return {
|
|
259
|
+
passed: false,
|
|
260
|
+
detail: "Info.plist has no platform slices",
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
passed: true,
|
|
264
|
+
detail: `Info.plist defines ${slices.length} platform slice${slices.length !== 1 ? "s" : ""}`,
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export const xcfMultipleSlices = {
|
|
270
|
+
id: "xcf-multi-slice",
|
|
271
|
+
category: "code",
|
|
272
|
+
label: "Supports multiple platform slices",
|
|
273
|
+
maxPoints: 15,
|
|
274
|
+
run: ({ xcframeworkMeta }) => {
|
|
275
|
+
if (!xcframeworkMeta)
|
|
276
|
+
return { passed: false, detail: "No XCFramework metadata" }
|
|
277
|
+
const slices = xcframeworkMeta.slices || []
|
|
278
|
+
const uniquePlatforms = new Set(
|
|
279
|
+
slices.map(s => (s.variant ? `${s.platform}-${s.variant}` : s.platform)),
|
|
280
|
+
)
|
|
281
|
+
const count = uniquePlatforms.size
|
|
282
|
+
let points
|
|
283
|
+
if (count >= 4) points = 15
|
|
284
|
+
else if (count === 3) points = 11
|
|
285
|
+
else if (count === 2) points = 9
|
|
286
|
+
else if (count === 1) points = 4
|
|
287
|
+
else points = 0
|
|
288
|
+
const labels = [...uniquePlatforms].join(", ")
|
|
289
|
+
return {
|
|
290
|
+
passed: points > 0,
|
|
291
|
+
points,
|
|
292
|
+
detail:
|
|
293
|
+
count > 0
|
|
294
|
+
? `${count} platform target${count !== 1 ? "s" : ""}: ${labels}`
|
|
295
|
+
: "No platform slices",
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export const xcfReasonableSize = {
|
|
301
|
+
id: "xcf-size",
|
|
302
|
+
category: "code",
|
|
303
|
+
label: "Reasonable framework size",
|
|
304
|
+
maxPoints: 5,
|
|
305
|
+
run: ({ unpackedSize }) => {
|
|
306
|
+
if (!unpackedSize)
|
|
307
|
+
return { passed: true, points: 5, detail: "No size data" }
|
|
308
|
+
const mb = unpackedSize / (1024 * 1024)
|
|
309
|
+
let points
|
|
310
|
+
if (mb <= 10) points = 5
|
|
311
|
+
else if (mb <= 50) points = 4
|
|
312
|
+
else if (mb <= 100) points = 3
|
|
313
|
+
else if (mb <= 200) points = 1
|
|
314
|
+
else points = 0
|
|
315
|
+
return {
|
|
316
|
+
passed: points > 0,
|
|
317
|
+
points,
|
|
318
|
+
detail: `${mb.toFixed(1)} MB`,
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export const xcfHasArchitectures = {
|
|
324
|
+
id: "xcf-architectures",
|
|
325
|
+
category: "code",
|
|
326
|
+
label: "Supports arm64 architecture",
|
|
327
|
+
maxPoints: 10,
|
|
328
|
+
run: ({ xcframeworkMeta }) => {
|
|
329
|
+
if (!xcframeworkMeta)
|
|
330
|
+
return { passed: false, detail: "No XCFramework metadata" }
|
|
331
|
+
const slices = xcframeworkMeta.slices || []
|
|
332
|
+
const allArchs = new Set(slices.flatMap(s => s.architectures || []))
|
|
333
|
+
const hasArm64 = allArchs.has("arm64")
|
|
334
|
+
return {
|
|
335
|
+
passed: hasArm64,
|
|
336
|
+
detail: hasArm64
|
|
337
|
+
? `Architectures: ${[...allArchs].join(", ")}`
|
|
338
|
+
: "Missing arm64 architecture support",
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// --- XCFramework health size check (MB-appropriate thresholds) ---
|
|
344
|
+
// The JS reasonableSize check uses KB thresholds which are unsuitable for
|
|
345
|
+
// binary frameworks. This replaces it in xcframeworkChecks with MB thresholds.
|
|
346
|
+
export const xcfHealthReasonableSize = {
|
|
347
|
+
id: "reasonable-size",
|
|
348
|
+
category: "health",
|
|
349
|
+
label: "Reasonable framework size",
|
|
350
|
+
maxPoints: 3,
|
|
351
|
+
run: ({ unpackedSize }) => {
|
|
352
|
+
if (!unpackedSize)
|
|
353
|
+
return { passed: true, points: 3, detail: "Size unknown" }
|
|
354
|
+
const mb = unpackedSize / (1024 * 1024)
|
|
355
|
+
let points
|
|
356
|
+
if (mb <= 10) points = 3
|
|
357
|
+
else if (mb <= 50) points = 2
|
|
358
|
+
else if (mb <= 100) points = 1
|
|
359
|
+
else points = 0
|
|
360
|
+
return {
|
|
361
|
+
passed: points > 0,
|
|
362
|
+
points,
|
|
363
|
+
detail: `${mb.toFixed(1)} MB`,
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// --- Export check sets ---
|
|
369
|
+
// We import the universal checks from checks.js and replace JS-specific ones
|
|
370
|
+
|
|
371
|
+
import {
|
|
372
|
+
authorVerified,
|
|
373
|
+
hasChangelog,
|
|
374
|
+
hasDescription,
|
|
375
|
+
hasHomepage,
|
|
376
|
+
hasKeywords,
|
|
377
|
+
hasLicense,
|
|
378
|
+
hasReadme,
|
|
379
|
+
hasRepository,
|
|
380
|
+
hasSkills,
|
|
381
|
+
maintenanceHealth,
|
|
382
|
+
noVulnerabilities,
|
|
383
|
+
readmeHasApi,
|
|
384
|
+
readmeHasUsage,
|
|
385
|
+
reasonableSize,
|
|
386
|
+
semverConsistency,
|
|
387
|
+
skillsComprehensive,
|
|
388
|
+
} from "./checks.js"
|
|
389
|
+
|
|
390
|
+
export const swiftChecks = [
|
|
391
|
+
// Documentation (22)
|
|
392
|
+
hasReadme,
|
|
393
|
+
swiftReadmeHasInstall,
|
|
394
|
+
readmeHasUsage,
|
|
395
|
+
readmeHasApi,
|
|
396
|
+
hasChangelog,
|
|
397
|
+
hasLicense,
|
|
398
|
+
// Code Quality (31)
|
|
399
|
+
hasPlatformDeclarations,
|
|
400
|
+
recentToolsVersion,
|
|
401
|
+
supportsMultiplePlatforms,
|
|
402
|
+
hasPublicAPI,
|
|
403
|
+
hasDocComments,
|
|
404
|
+
smallDepsSwift,
|
|
405
|
+
// Testing (11)
|
|
406
|
+
swiftHasTestTargets,
|
|
407
|
+
swiftHasTestScript,
|
|
408
|
+
// Health (36)
|
|
409
|
+
hasDescription,
|
|
410
|
+
hasKeywords,
|
|
411
|
+
hasRepository,
|
|
412
|
+
hasHomepage,
|
|
413
|
+
reasonableSize,
|
|
414
|
+
noVulnerabilities,
|
|
415
|
+
maintenanceHealth,
|
|
416
|
+
semverConsistency,
|
|
417
|
+
authorVerified,
|
|
418
|
+
hasSkills,
|
|
419
|
+
skillsComprehensive,
|
|
420
|
+
]
|
|
421
|
+
|
|
422
|
+
export const xcframeworkChecks = [
|
|
423
|
+
// Documentation (22)
|
|
424
|
+
hasReadme,
|
|
425
|
+
swiftReadmeHasInstall,
|
|
426
|
+
readmeHasUsage,
|
|
427
|
+
readmeHasApi,
|
|
428
|
+
hasChangelog,
|
|
429
|
+
hasLicense,
|
|
430
|
+
// Code Quality (40) — XCFramework-specific
|
|
431
|
+
xcfHasValidPlist,
|
|
432
|
+
xcfMultipleSlices,
|
|
433
|
+
xcfReasonableSize,
|
|
434
|
+
xcfHasArchitectures,
|
|
435
|
+
// Health (38) — XCF keeps original maintenance-health (5) and author-verified (4)
|
|
436
|
+
hasDescription,
|
|
437
|
+
hasKeywords,
|
|
438
|
+
hasRepository,
|
|
439
|
+
hasHomepage,
|
|
440
|
+
xcfHealthReasonableSize,
|
|
441
|
+
noVulnerabilities,
|
|
442
|
+
{ ...maintenanceHealth, maxPoints: 5 },
|
|
443
|
+
semverConsistency,
|
|
444
|
+
{ ...authorVerified, maxPoints: 4 },
|
|
445
|
+
hasSkills,
|
|
446
|
+
skillsComprehensive,
|
|
447
|
+
]
|