@lpm-registry/cli 0.58.0 → 0.59.1

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 +25 -44
  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 +42 -82
  6. package/scripts/install-binary.js +219 -550
  7. package/CHANGELOG.md +0 -197
  8. package/LICENSE +0 -15
  9. package/index.d.ts +0 -131
  10. package/index.js +0 -31
  11. package/lib/api.js +0 -398
  12. package/lib/commands/add.js +0 -1217
  13. package/lib/commands/audit.js +0 -283
  14. package/lib/commands/cache.js +0 -209
  15. package/lib/commands/check-name.js +0 -112
  16. package/lib/commands/config.js +0 -186
  17. package/lib/commands/doctor.js +0 -140
  18. package/lib/commands/info.js +0 -215
  19. package/lib/commands/init.js +0 -146
  20. package/lib/commands/install.js +0 -264
  21. package/lib/commands/login.js +0 -595
  22. package/lib/commands/logout.js +0 -94
  23. package/lib/commands/marketplace-compare.js +0 -164
  24. package/lib/commands/marketplace-earnings.js +0 -89
  25. package/lib/commands/mcp-setup.js +0 -363
  26. package/lib/commands/npmrc.js +0 -178
  27. package/lib/commands/open.js +0 -82
  28. package/lib/commands/outdated.js +0 -291
  29. package/lib/commands/pool-stats.js +0 -100
  30. package/lib/commands/publish.js +0 -949
  31. package/lib/commands/quality.js +0 -211
  32. package/lib/commands/remove.js +0 -82
  33. package/lib/commands/run.js +0 -14
  34. package/lib/commands/search.js +0 -143
  35. package/lib/commands/setup.js +0 -221
  36. package/lib/commands/skills.js +0 -898
  37. package/lib/commands/token-rotate.js +0 -25
  38. package/lib/commands/uninstall.js +0 -107
  39. package/lib/commands/whoami.js +0 -129
  40. package/lib/config.js +0 -328
  41. package/lib/constants.js +0 -190
  42. package/lib/ecosystem.js +0 -501
  43. package/lib/editor-skills.js +0 -321
  44. package/lib/editors.js +0 -215
  45. package/lib/import-rewriter.js +0 -364
  46. package/lib/install-targets/mcp-server.js +0 -245
  47. package/lib/install-targets/vscode-extension.js +0 -178
  48. package/lib/install-targets.js +0 -82
  49. package/lib/integrity.js +0 -179
  50. package/lib/lpm-config-prompts.js +0 -102
  51. package/lib/lpm-config.js +0 -408
  52. package/lib/oidc.js +0 -166
  53. package/lib/project-utils.js +0 -152
  54. package/lib/quality/checks.js +0 -687
  55. package/lib/quality/display.js +0 -155
  56. package/lib/quality/score.js +0 -118
  57. package/lib/quality/swift-checks.js +0 -447
  58. package/lib/safe-path.js +0 -180
  59. package/lib/secure-store.js +0 -321
  60. package/lib/swift-project.js +0 -637
  61. package/lib/ui.js +0 -40
  62. package/scripts/__tests__/install-binary.test.js +0 -346
@@ -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