@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +15 -0
  3. package/README.md +406 -0
  4. package/bin/lpm.js +334 -0
  5. package/index.d.ts +131 -0
  6. package/index.js +31 -0
  7. package/lib/api.js +324 -0
  8. package/lib/commands/add.js +1217 -0
  9. package/lib/commands/audit.js +283 -0
  10. package/lib/commands/cache.js +209 -0
  11. package/lib/commands/check-name.js +112 -0
  12. package/lib/commands/config.js +174 -0
  13. package/lib/commands/doctor.js +142 -0
  14. package/lib/commands/info.js +215 -0
  15. package/lib/commands/init.js +146 -0
  16. package/lib/commands/install.js +217 -0
  17. package/lib/commands/login.js +547 -0
  18. package/lib/commands/logout.js +94 -0
  19. package/lib/commands/marketplace-compare.js +164 -0
  20. package/lib/commands/marketplace-earnings.js +89 -0
  21. package/lib/commands/mcp-setup.js +363 -0
  22. package/lib/commands/open.js +82 -0
  23. package/lib/commands/outdated.js +291 -0
  24. package/lib/commands/pool-stats.js +100 -0
  25. package/lib/commands/publish.js +707 -0
  26. package/lib/commands/quality.js +211 -0
  27. package/lib/commands/remove.js +82 -0
  28. package/lib/commands/run.js +14 -0
  29. package/lib/commands/search.js +143 -0
  30. package/lib/commands/setup.js +92 -0
  31. package/lib/commands/skills.js +863 -0
  32. package/lib/commands/token-rotate.js +25 -0
  33. package/lib/commands/whoami.js +129 -0
  34. package/lib/config.js +240 -0
  35. package/lib/constants.js +190 -0
  36. package/lib/ecosystem.js +501 -0
  37. package/lib/editors.js +215 -0
  38. package/lib/import-rewriter.js +364 -0
  39. package/lib/install-targets/mcp-server.js +245 -0
  40. package/lib/install-targets/vscode-extension.js +178 -0
  41. package/lib/install-targets.js +82 -0
  42. package/lib/integrity.js +179 -0
  43. package/lib/lpm-config-prompts.js +102 -0
  44. package/lib/lpm-config.js +408 -0
  45. package/lib/project-utils.js +152 -0
  46. package/lib/quality/checks.js +654 -0
  47. package/lib/quality/display.js +139 -0
  48. package/lib/quality/score.js +115 -0
  49. package/lib/quality/swift-checks.js +447 -0
  50. package/lib/safe-path.js +180 -0
  51. package/lib/secure-store.js +288 -0
  52. package/lib/swift-project.js +637 -0
  53. package/lib/ui.js +40 -0
  54. 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
+ ]