@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,291 @@
1
+ /**
2
+ * Outdated Command
3
+ *
4
+ * Check for outdated dependencies in the project.
5
+ *
6
+ * Usage:
7
+ * lpm outdated [options]
8
+ *
9
+ * Options:
10
+ * --json Output in JSON format
11
+ * --all Show all dependencies, not just outdated ones
12
+ *
13
+ * @module cli/lib/commands/outdated
14
+ */
15
+
16
+ import { existsSync, readFileSync } from "node:fs"
17
+ import { join } from "node:path"
18
+ import chalk from "chalk"
19
+ import ora from "ora"
20
+ import { post } from "../api.js"
21
+
22
+ /**
23
+ * Read and parse package.json from current directory.
24
+ * @returns {Object | null}
25
+ */
26
+ function readPackageJson() {
27
+ const packageJsonPath = join(process.cwd(), "package.json")
28
+
29
+ if (!existsSync(packageJsonPath)) {
30
+ return null
31
+ }
32
+
33
+ try {
34
+ const content = readFileSync(packageJsonPath, "utf8")
35
+ return JSON.parse(content)
36
+ } catch {
37
+ return null
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Read package-lock.json for current versions.
43
+ * @returns {Object | null}
44
+ */
45
+ function readPackageLock() {
46
+ const lockPath = join(process.cwd(), "package-lock.json")
47
+
48
+ if (!existsSync(lockPath)) {
49
+ return null
50
+ }
51
+
52
+ try {
53
+ const content = readFileSync(lockPath, "utf8")
54
+ const lock = JSON.parse(content)
55
+ return lock.packages || lock.dependencies || null
56
+ } catch {
57
+ return null
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Compare semver versions.
63
+ * @param {string} current
64
+ * @param {string} latest
65
+ * @returns {'major' | 'minor' | 'patch' | 'same' | 'unknown'}
66
+ */
67
+ function getUpdateType(current, latest) {
68
+ const parseVersion = v => {
69
+ const match = v.match(/^(\d+)\.(\d+)\.(\d+)/)
70
+ if (!match) return null
71
+ return [
72
+ parseInt(match[1], 10),
73
+ parseInt(match[2], 10),
74
+ parseInt(match[3], 10),
75
+ ]
76
+ }
77
+
78
+ const currentParts = parseVersion(current)
79
+ const latestParts = parseVersion(latest)
80
+
81
+ if (!currentParts || !latestParts) return "unknown"
82
+
83
+ if (latestParts[0] > currentParts[0]) return "major"
84
+ if (latestParts[1] > currentParts[1]) return "minor"
85
+ if (latestParts[2] > currentParts[2]) return "patch"
86
+
87
+ return "same"
88
+ }
89
+
90
+ /**
91
+ * Get color for update type.
92
+ * @param {string} type
93
+ * @returns {Function}
94
+ */
95
+ function getUpdateColor(type) {
96
+ switch (type) {
97
+ case "major":
98
+ return chalk.red
99
+ case "minor":
100
+ return chalk.yellow
101
+ case "patch":
102
+ return chalk.green
103
+ default:
104
+ return chalk.dim
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Execute the outdated command.
110
+ *
111
+ * @param {Object} options - Command options
112
+ * @param {boolean} [options.json] - Output as JSON
113
+ * @param {boolean} [options.all] - Show all dependencies
114
+ */
115
+ export async function outdated(options = {}) {
116
+ const spinner = ora("Reading dependencies...").start()
117
+
118
+ // Read package.json
119
+ const packageJson = readPackageJson()
120
+
121
+ if (!packageJson) {
122
+ spinner.fail(chalk.red("No package.json found in current directory."))
123
+ process.exit(1)
124
+ }
125
+
126
+ const dependencies = {
127
+ ...(packageJson.dependencies || {}),
128
+ }
129
+
130
+ const devDependencies = {
131
+ ...(packageJson.devDependencies || {}),
132
+ }
133
+
134
+ const allDeps = { ...dependencies, ...devDependencies }
135
+ const depCount = Object.keys(allDeps).length
136
+
137
+ if (depCount === 0) {
138
+ spinner.succeed(chalk.green("No dependencies to check."))
139
+ return
140
+ }
141
+
142
+ // Get current versions from lock file
143
+ const lockData = readPackageLock()
144
+
145
+ // Build dependency list
146
+ const depList = Object.entries(allDeps).map(([name, version]) => {
147
+ let currentVersion = version.replace(/^[\^~]/, "")
148
+
149
+ if (lockData) {
150
+ const lockEntry = lockData[name] || lockData[`node_modules/${name}`]
151
+ if (lockEntry?.version) {
152
+ currentVersion = lockEntry.version
153
+ }
154
+ }
155
+
156
+ return {
157
+ name,
158
+ currentVersion,
159
+ wantedVersion: version,
160
+ isDev: name in devDependencies,
161
+ }
162
+ })
163
+
164
+ spinner.text = `Checking ${depCount} dependencies...`
165
+
166
+ try {
167
+ const response = await post(
168
+ "/outdated",
169
+ {
170
+ dependencies: depList.map(d => ({
171
+ name: d.name,
172
+ version: d.currentVersion,
173
+ })),
174
+ },
175
+ {
176
+ onRetry: (attempt, max) => {
177
+ spinner.text = `Checking (retry ${attempt}/${max})...`
178
+ },
179
+ },
180
+ )
181
+
182
+ if (!response.ok) {
183
+ const data = await response.json().catch(() => ({}))
184
+ throw new Error(data.error || "Outdated check failed.")
185
+ }
186
+
187
+ const data = await response.json()
188
+ const latestVersions = data.packages || {}
189
+
190
+ spinner.stop()
191
+
192
+ // Merge latest versions with our data
193
+ const results = depList.map(dep => {
194
+ const latest = latestVersions[dep.name]?.latest || dep.currentVersion
195
+ const updateType = getUpdateType(dep.currentVersion, latest)
196
+
197
+ return {
198
+ ...dep,
199
+ latestVersion: latest,
200
+ updateType,
201
+ isOutdated: updateType !== "same" && updateType !== "unknown",
202
+ }
203
+ })
204
+
205
+ // Filter if not showing all
206
+ const displayResults = options.all
207
+ ? results
208
+ : results.filter(r => r.isOutdated)
209
+
210
+ // JSON output
211
+ if (options.json) {
212
+ console.log(JSON.stringify(displayResults, null, 2))
213
+ return
214
+ }
215
+
216
+ // No outdated packages
217
+ if (displayResults.length === 0) {
218
+ console.log(
219
+ chalk.green(`\n✓ All ${depCount} dependencies are up to date.\n`),
220
+ )
221
+ return
222
+ }
223
+
224
+ // Display results
225
+ const outdatedCount = results.filter(r => r.isOutdated).length
226
+ console.log(
227
+ chalk.bold(
228
+ `\n${outdatedCount} outdated package${outdatedCount > 1 ? "s" : ""} in ${depCount} dependencies.\n`,
229
+ ),
230
+ )
231
+
232
+ // Calculate column widths
233
+ const maxNameLen = Math.max(...displayResults.map(r => r.name.length), 10)
234
+ const maxCurrentLen = Math.max(
235
+ ...displayResults.map(r => r.currentVersion.length),
236
+ 7,
237
+ )
238
+ const maxLatestLen = Math.max(
239
+ ...displayResults.map(r => r.latestVersion.length),
240
+ 6,
241
+ )
242
+
243
+ // Header
244
+ console.log(
245
+ ` ${chalk.dim("Package".padEnd(maxNameLen))} ${chalk.dim("Current".padEnd(maxCurrentLen))} ${chalk.dim("Latest".padEnd(maxLatestLen))} ${chalk.dim("Type")}`,
246
+ )
247
+ console.log(
248
+ chalk.dim(
249
+ ` ${"─".repeat(maxNameLen + maxCurrentLen + maxLatestLen + 20)}`,
250
+ ),
251
+ )
252
+
253
+ // Rows
254
+ for (const result of displayResults) {
255
+ const name = result.name.padEnd(maxNameLen)
256
+ const current = result.currentVersion.padEnd(maxCurrentLen)
257
+ const latest = result.latestVersion.padEnd(maxLatestLen)
258
+ const color = getUpdateColor(result.updateType)
259
+ const devTag = result.isDev ? chalk.dim(" (dev)") : ""
260
+
261
+ console.log(
262
+ ` ${chalk.cyan(name)} ${chalk.dim(current)} ${color(latest)} ${color(result.updateType)}${devTag}`,
263
+ )
264
+ }
265
+
266
+ console.log("")
267
+
268
+ // Summary
269
+ const majorCount = results.filter(r => r.updateType === "major").length
270
+ const minorCount = results.filter(r => r.updateType === "minor").length
271
+ const patchCount = results.filter(r => r.updateType === "patch").length
272
+
273
+ const parts = []
274
+ if (majorCount > 0) parts.push(chalk.red(`${majorCount} major`))
275
+ if (minorCount > 0) parts.push(chalk.yellow(`${minorCount} minor`))
276
+ if (patchCount > 0) parts.push(chalk.green(`${patchCount} patch`))
277
+
278
+ if (parts.length > 0) {
279
+ console.log(
280
+ ` ${parts.join(", ")} update${outdatedCount > 1 ? "s" : ""} available.`,
281
+ )
282
+ console.log("")
283
+ }
284
+ } catch (error) {
285
+ spinner.fail(chalk.red("Outdated check failed."))
286
+ console.error(chalk.red(` ${error.message}`))
287
+ process.exit(1)
288
+ }
289
+ }
290
+
291
+ export default outdated
@@ -0,0 +1,100 @@
1
+ import chalk from "chalk"
2
+ import ora from "ora"
3
+ import { get } from "../api.js"
4
+
5
+ /**
6
+ * Format cents to a dollar string.
7
+ * @param {number} cents
8
+ * @returns {string}
9
+ */
10
+ function formatCents(cents) {
11
+ if (!cents && cents !== 0) return "$0.00"
12
+ return `$${(cents / 100).toFixed(2)}`
13
+ }
14
+
15
+ /**
16
+ * Fetch and display Pool earnings statistics for the authenticated user.
17
+ *
18
+ * @param {Object} [options]
19
+ * @param {boolean} [options.json] - Output as JSON
20
+ */
21
+ export async function poolStats(options = {}) {
22
+ const spinner = options.json
23
+ ? null
24
+ : ora("Fetching pool statistics...").start()
25
+
26
+ try {
27
+ const response = await get("/pool/stats", {
28
+ skipRetry: false,
29
+ onRetry: spinner
30
+ ? (attempt, max) => {
31
+ spinner.text = `Fetching (retry ${attempt}/${max})...`
32
+ }
33
+ : undefined,
34
+ })
35
+
36
+ const data = await response.json().catch(() => ({}))
37
+
38
+ if (!response.ok) {
39
+ throw new Error(data.error || `Request failed: ${response.status}`)
40
+ }
41
+
42
+ if (options.json) {
43
+ console.log(JSON.stringify(data, null, 2))
44
+ return
45
+ }
46
+
47
+ if (spinner) spinner.stop()
48
+
49
+ const packages = data.packages || []
50
+
51
+ console.log("")
52
+ console.log(chalk.bold(" Pool Earnings — Current Month"))
53
+ console.log(chalk.dim(` Period: ${data.billingPeriod || "N/A"}`))
54
+ console.log("")
55
+
56
+ // Summary stats
57
+ console.log(
58
+ ` ${chalk.bold("Your Estimate:")} ${chalk.green(formatCents(data.estimatedEarningsCents))}`,
59
+ )
60
+ console.log("")
61
+
62
+ if (packages.length === 0) {
63
+ console.log(chalk.dim(" No pool packages found."))
64
+ console.log(
65
+ chalk.dim(
66
+ " Publish a package with Pool distribution to start earning.",
67
+ ),
68
+ )
69
+ console.log("")
70
+ return
71
+ }
72
+
73
+ // Table header
74
+ const nameWidth = Math.max(30, ...packages.map(p => p.name.length + 2))
75
+ console.log(
76
+ ` ${chalk.dim("Package".padEnd(nameWidth))}${chalk.dim("Installs".padStart(10))}${chalk.dim("Share %".padStart(10))}${chalk.dim("Earnings".padStart(12))}`,
77
+ )
78
+ console.log(chalk.dim(` ${"─".repeat(nameWidth + 32)}`))
79
+
80
+ for (const pkg of packages) {
81
+ const name = pkg.name.padEnd(nameWidth)
82
+ const installs = (pkg.installCount || 0).toLocaleString().padStart(10)
83
+ const share = `${(pkg.sharePercentage || 0).toFixed(2)}%`.padStart(10)
84
+ const earnings = formatCents(pkg.estimatedEarningsCents).padStart(12)
85
+ console.log(` ${name}${installs}${share}${chalk.green(earnings)}`)
86
+ }
87
+
88
+ console.log("")
89
+ } catch (error) {
90
+ if (spinner) spinner.fail(chalk.red("Failed to fetch pool statistics."))
91
+ if (options.json) {
92
+ console.log(JSON.stringify({ error: error.message }))
93
+ } else {
94
+ console.error(chalk.red(` ${error.message}`))
95
+ }
96
+ process.exit(1)
97
+ }
98
+ }
99
+
100
+ export default poolStats