@lpm-registry/cli 0.57.0 → 0.59.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 (62) hide show
  1. package/README.md +21 -45
  2. package/bin/lpm.js +2 -354
  3. package/bin/lpx.js +4 -0
  4. package/bin/native.js +154 -0
  5. package/package.json +40 -82
  6. package/CHANGELOG.md +0 -197
  7. package/LICENSE +0 -15
  8. package/index.d.ts +0 -131
  9. package/index.js +0 -31
  10. package/lib/api.js +0 -398
  11. package/lib/commands/add.js +0 -1217
  12. package/lib/commands/audit.js +0 -283
  13. package/lib/commands/cache.js +0 -209
  14. package/lib/commands/check-name.js +0 -112
  15. package/lib/commands/config.js +0 -186
  16. package/lib/commands/doctor.js +0 -140
  17. package/lib/commands/info.js +0 -215
  18. package/lib/commands/init.js +0 -146
  19. package/lib/commands/install.js +0 -264
  20. package/lib/commands/login.js +0 -595
  21. package/lib/commands/logout.js +0 -94
  22. package/lib/commands/marketplace-compare.js +0 -164
  23. package/lib/commands/marketplace-earnings.js +0 -89
  24. package/lib/commands/mcp-setup.js +0 -363
  25. package/lib/commands/npmrc.js +0 -178
  26. package/lib/commands/open.js +0 -82
  27. package/lib/commands/outdated.js +0 -291
  28. package/lib/commands/pool-stats.js +0 -100
  29. package/lib/commands/publish.js +0 -949
  30. package/lib/commands/quality.js +0 -211
  31. package/lib/commands/remove.js +0 -82
  32. package/lib/commands/run.js +0 -14
  33. package/lib/commands/search.js +0 -143
  34. package/lib/commands/setup.js +0 -221
  35. package/lib/commands/skills.js +0 -898
  36. package/lib/commands/token-rotate.js +0 -25
  37. package/lib/commands/uninstall.js +0 -107
  38. package/lib/commands/whoami.js +0 -129
  39. package/lib/config.js +0 -328
  40. package/lib/constants.js +0 -190
  41. package/lib/ecosystem.js +0 -501
  42. package/lib/editor-skills.js +0 -321
  43. package/lib/editors.js +0 -215
  44. package/lib/import-rewriter.js +0 -364
  45. package/lib/install-targets/mcp-server.js +0 -245
  46. package/lib/install-targets/vscode-extension.js +0 -178
  47. package/lib/install-targets.js +0 -82
  48. package/lib/integrity.js +0 -179
  49. package/lib/lpm-config-prompts.js +0 -102
  50. package/lib/lpm-config.js +0 -408
  51. package/lib/oidc.js +0 -166
  52. package/lib/project-utils.js +0 -152
  53. package/lib/quality/checks.js +0 -687
  54. package/lib/quality/display.js +0 -155
  55. package/lib/quality/score.js +0 -118
  56. package/lib/quality/swift-checks.js +0 -447
  57. package/lib/safe-path.js +0 -180
  58. package/lib/secure-store.js +0 -321
  59. package/lib/swift-project.js +0 -637
  60. package/lib/ui.js +0 -40
  61. package/scripts/__tests__/install-binary.test.js +0 -346
  62. package/scripts/install-binary.js +0 -571
@@ -1,178 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { request } from "../api.js";
4
- import { getRegistryUrl, getToken } from "../config.js";
5
- import { createSpinner, log, printHeader } from "../ui.js";
6
-
7
- /**
8
- * Generate a read-only .npmrc token for local development.
9
- * Creates a 30-day scoped token and writes it to the project's .npmrc.
10
- * Also ensures .npmrc is in .gitignore to prevent accidental commits.
11
- */
12
- export async function npmrc(options) {
13
- printHeader();
14
-
15
- const token = await getToken();
16
- if (!token) {
17
- log.error(
18
- 'You must be logged in to generate an .npmrc token. Run "lpm login" first.',
19
- );
20
- process.exit(1);
21
- }
22
-
23
- const projectRoot = process.cwd();
24
- const npmrcPath = path.join(projectRoot, ".npmrc");
25
- const gitignorePath = path.join(projectRoot, ".gitignore");
26
-
27
- // Check for package.json
28
- const pkgJsonPath = path.join(projectRoot, "package.json");
29
- if (!fs.existsSync(pkgJsonPath)) {
30
- log.warn("No package.json found. Run this command in your project root.");
31
- }
32
-
33
- // Derive project name for the token label
34
- let projectName = "project";
35
- if (fs.existsSync(pkgJsonPath)) {
36
- try {
37
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
38
- if (pkg.name) {
39
- projectName = pkg.name.replace(/[^a-zA-Z0-9._-]/g, "-");
40
- }
41
- } catch {
42
- // Invalid package.json — use default name
43
- }
44
- }
45
-
46
- const expiryDays = parseInt(options?.days, 10) || 30;
47
- const spinner = createSpinner("Creating read-only token...").start();
48
-
49
- // Call server to create a scoped read-only token
50
- let readToken;
51
- let expiresAt;
52
- try {
53
- const tokenName = `npmrc-${projectName}-${new Date().toISOString().slice(0, 10)}`;
54
- const response = await request("/-/token/create", {
55
- method: "POST",
56
- body: JSON.stringify({
57
- scope: "read",
58
- name: tokenName,
59
- expiryDays,
60
- }),
61
- headers: { "Content-Type": "application/json" },
62
- });
63
-
64
- if (!response.ok) {
65
- const text = await response.text();
66
- throw new Error(`Token creation failed: ${response.status} ${text}`);
67
- }
68
-
69
- const data = await response.json();
70
- readToken = data.token;
71
- expiresAt = data.expiresAt;
72
- spinner.succeed("Read-only token created.");
73
- } catch (error) {
74
- spinner.fail(`Failed to create token: ${error.message}`);
75
- process.exit(1);
76
- }
77
-
78
- // Build .npmrc content
79
- const registryUrl = getRegistryUrl();
80
- const fullRegistryUrl = registryUrl.endsWith("/api/registry")
81
- ? registryUrl
82
- : `${registryUrl}/api/registry`;
83
- const registryHost = fullRegistryUrl.replace(/^https?:/, "");
84
-
85
- // Default: scoped (only @lpm.dev packages through lpm.dev).
86
- // --proxy: route all npm traffic through lpm.dev (Pro/Org feature).
87
- let useScoped = !options?.proxy;
88
-
89
- // Read existing .npmrc and check for custom default registry
90
- let npmrcContent = "";
91
- if (fs.existsSync(npmrcPath)) {
92
- const existingContent = fs.readFileSync(npmrcPath, "utf8");
93
-
94
- // Detect custom default registry (not npmjs.org and not lpm.dev)
95
- if (!useScoped) {
96
- const registryLine = existingContent
97
- .split("\n")
98
- .find(
99
- (line) =>
100
- line.match(/^registry=/) &&
101
- !line.includes("registry.npmjs.org") &&
102
- !line.includes("lpm.dev"),
103
- );
104
- if (registryLine) {
105
- log.warn(`Found existing default registry: ${registryLine.trim()}`);
106
- log.warn("Using --scoped mode to avoid overriding it.");
107
- log.warn(
108
- "To use LPM as your default registry, remove the existing registry= line first.",
109
- );
110
- useScoped = true;
111
- }
112
- }
113
-
114
- // Remove old LPM config lines (both scoped and unscoped formats)
115
- npmrcContent = existingContent
116
- .split("\n")
117
- .filter((line) => {
118
- return (
119
- !line.includes("@lpm.dev:registry") &&
120
- !line.match(/^registry=.*lpm\.dev/) &&
121
- !line.includes("lpm.dev/api/registry/:_authToken") &&
122
- !line.includes("_authToken=lpm_") &&
123
- // biome-ignore lint/suspicious/noTemplateCurlyInString: matching literal npm token placeholder
124
- !line.includes("_authToken=${LPM_TOKEN}") &&
125
- !line.includes("# LPM Registry")
126
- );
127
- })
128
- .join("\n")
129
- .trim();
130
- }
131
-
132
- const registryLine = useScoped
133
- ? `@lpm.dev:registry=${fullRegistryUrl}`
134
- : `registry=${fullRegistryUrl}`;
135
-
136
- const lpmConfig = `# LPM Registry (generated by lpm setup local — do not commit)
137
- ${registryLine}
138
- ${registryHost}/:_authToken=${readToken}`;
139
-
140
- npmrcContent = npmrcContent ? `${npmrcContent}\n\n${lpmConfig}` : lpmConfig;
141
- fs.writeFileSync(npmrcPath, `${npmrcContent}\n`);
142
-
143
- // Ensure .npmrc is in .gitignore
144
- let gitignoreUpdated = false;
145
- if (fs.existsSync(gitignorePath)) {
146
- const gitignore = fs.readFileSync(gitignorePath, "utf8");
147
- if (!gitignore.split("\n").some((line) => line.trim() === ".npmrc")) {
148
- fs.writeFileSync(gitignorePath, `${gitignore.trimEnd()}\n.npmrc\n`);
149
- gitignoreUpdated = true;
150
- }
151
- } else {
152
- fs.writeFileSync(gitignorePath, ".npmrc\n");
153
- gitignoreUpdated = true;
154
- }
155
-
156
- // Output
157
- console.log("");
158
- log.success(".npmrc configured with read-only LPM token.");
159
- if (gitignoreUpdated) {
160
- log.info(".npmrc added to .gitignore to prevent token leaks.");
161
- }
162
-
163
- if (useScoped) {
164
- log.info("Only @lpm.dev packages will route through lpm.dev.");
165
- } else {
166
- log.info("All packages (LPM + npm) will route through lpm.dev.");
167
- log.info("Note: First install may update resolved URLs in your lockfile.");
168
- }
169
-
170
- const expiryDate = new Date(expiresAt).toLocaleDateString("en-US", {
171
- year: "numeric",
172
- month: "long",
173
- day: "numeric",
174
- });
175
- log.info(`Token expires: ${expiryDate}`);
176
- log.info("Run `lpm setup local` again to refresh when expired.");
177
- console.log("");
178
- }
@@ -1,82 +0,0 @@
1
- import fs from "node:fs"
2
- import path from "node:path"
3
- import open from "open"
4
- import { request } from "../api.js"
5
- import { getRegistryUrl } from "../config.js"
6
- import { createSpinner } from "../ui.js"
7
-
8
- /**
9
- * Parse owner from LPM package name.
10
- * @param {string} name - Package name (e.g., '@lpm.dev/owner.pkg-name')
11
- * @returns {{ owner: string, pkgName: string } | null}
12
- */
13
- function parseOwner(name) {
14
- if (name.startsWith("@lpm.dev/")) {
15
- const nameWithOwner = name.replace("@lpm.dev/", "")
16
- const dotIndex = nameWithOwner.indexOf(".")
17
- if (dotIndex === -1) return null
18
- return {
19
- owner: nameWithOwner.substring(0, dotIndex),
20
- pkgName: nameWithOwner.substring(dotIndex + 1),
21
- }
22
- }
23
- return null
24
- }
25
-
26
- export async function openDashboard() {
27
- const spinner = createSpinner("Opening dashboard...").start()
28
- const registryUrl = getRegistryUrl()
29
-
30
- // Default URL
31
- let url = `${registryUrl}/dashboard`
32
-
33
- // Check if we are in a package directory
34
- const pkgPath = path.join(process.cwd(), "package.json")
35
- if (fs.existsSync(pkgPath)) {
36
- try {
37
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
38
- const parsed = parseOwner(pkg.name)
39
-
40
- if (parsed) {
41
- const { owner, pkgName } = parsed
42
- spinner.text = `Detected package ${pkg.name}. Checking owner...`
43
-
44
- // Fetch user info to determine if owner is personal or org
45
- try {
46
- const response = await request("/-/whoami")
47
- if (response.ok) {
48
- const data = await response.json()
49
-
50
- // Check if owner matches user's personal username
51
- if (data.profile_username && data.profile_username === owner) {
52
- url = `${registryUrl}/dashboard/packages`
53
- spinner.text = `Opening personal packages dashboard...`
54
- }
55
- // Check if owner matches one of user's organizations
56
- else if (data.organizations?.some(org => org.slug === owner)) {
57
- url = `${registryUrl}/dashboard/orgs/${owner}/packages`
58
- spinner.text = `Opening ${owner} organization packages...`
59
- }
60
- // Owner not recognized, fall back to public package page
61
- else {
62
- url = `${registryUrl}/${owner}.${pkgName}`
63
- spinner.text = `Opening public package page...`
64
- }
65
- } else {
66
- // Not authenticated, open public package page
67
- url = `${registryUrl}/${owner}.${pkgName}`
68
- spinner.text = `Opening public package page...`
69
- }
70
- } catch (_apiError) {
71
- // API error, fall back to dashboard
72
- url = `${registryUrl}/dashboard`
73
- }
74
- }
75
- } catch (_e) {
76
- // ignore JSON parse errors
77
- }
78
- }
79
-
80
- await open(url)
81
- spinner.succeed(`Opened ${url}`)
82
- }
@@ -1,291 +0,0 @@
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
@@ -1,100 +0,0 @@
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