@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.
- package/README.md +25 -44
- package/bin/lpm.js +2 -354
- package/bin/lpx.js +4 -0
- package/bin/native.js +154 -0
- package/package.json +42 -82
- package/scripts/install-binary.js +219 -550
- package/CHANGELOG.md +0 -197
- package/LICENSE +0 -15
- package/index.d.ts +0 -131
- package/index.js +0 -31
- package/lib/api.js +0 -398
- package/lib/commands/add.js +0 -1217
- package/lib/commands/audit.js +0 -283
- package/lib/commands/cache.js +0 -209
- package/lib/commands/check-name.js +0 -112
- package/lib/commands/config.js +0 -186
- package/lib/commands/doctor.js +0 -140
- package/lib/commands/info.js +0 -215
- package/lib/commands/init.js +0 -146
- package/lib/commands/install.js +0 -264
- package/lib/commands/login.js +0 -595
- package/lib/commands/logout.js +0 -94
- package/lib/commands/marketplace-compare.js +0 -164
- package/lib/commands/marketplace-earnings.js +0 -89
- package/lib/commands/mcp-setup.js +0 -363
- package/lib/commands/npmrc.js +0 -178
- package/lib/commands/open.js +0 -82
- package/lib/commands/outdated.js +0 -291
- package/lib/commands/pool-stats.js +0 -100
- package/lib/commands/publish.js +0 -949
- package/lib/commands/quality.js +0 -211
- package/lib/commands/remove.js +0 -82
- package/lib/commands/run.js +0 -14
- package/lib/commands/search.js +0 -143
- package/lib/commands/setup.js +0 -221
- package/lib/commands/skills.js +0 -898
- package/lib/commands/token-rotate.js +0 -25
- package/lib/commands/uninstall.js +0 -107
- package/lib/commands/whoami.js +0 -129
- package/lib/config.js +0 -328
- package/lib/constants.js +0 -190
- package/lib/ecosystem.js +0 -501
- package/lib/editor-skills.js +0 -321
- package/lib/editors.js +0 -215
- package/lib/import-rewriter.js +0 -364
- package/lib/install-targets/mcp-server.js +0 -245
- package/lib/install-targets/vscode-extension.js +0 -178
- package/lib/install-targets.js +0 -82
- package/lib/integrity.js +0 -179
- package/lib/lpm-config-prompts.js +0 -102
- package/lib/lpm-config.js +0 -408
- package/lib/oidc.js +0 -166
- package/lib/project-utils.js +0 -152
- package/lib/quality/checks.js +0 -687
- package/lib/quality/display.js +0 -155
- package/lib/quality/score.js +0 -118
- package/lib/quality/swift-checks.js +0 -447
- package/lib/safe-path.js +0 -180
- package/lib/secure-store.js +0 -321
- package/lib/swift-project.js +0 -637
- package/lib/ui.js +0 -40
- package/scripts/__tests__/install-binary.test.js +0 -346
package/lib/commands/npmrc.js
DELETED
|
@@ -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
|
-
}
|
package/lib/commands/open.js
DELETED
|
@@ -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
|
-
}
|
package/lib/commands/outdated.js
DELETED
|
@@ -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
|