@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.
- package/CHANGELOG.md +36 -0
- package/LICENSE +15 -0
- package/README.md +406 -0
- package/bin/lpm.js +334 -0
- package/index.d.ts +131 -0
- package/index.js +31 -0
- package/lib/api.js +324 -0
- package/lib/commands/add.js +1217 -0
- package/lib/commands/audit.js +283 -0
- package/lib/commands/cache.js +209 -0
- package/lib/commands/check-name.js +112 -0
- package/lib/commands/config.js +174 -0
- package/lib/commands/doctor.js +142 -0
- package/lib/commands/info.js +215 -0
- package/lib/commands/init.js +146 -0
- package/lib/commands/install.js +217 -0
- package/lib/commands/login.js +547 -0
- package/lib/commands/logout.js +94 -0
- package/lib/commands/marketplace-compare.js +164 -0
- package/lib/commands/marketplace-earnings.js +89 -0
- package/lib/commands/mcp-setup.js +363 -0
- package/lib/commands/open.js +82 -0
- package/lib/commands/outdated.js +291 -0
- package/lib/commands/pool-stats.js +100 -0
- package/lib/commands/publish.js +707 -0
- package/lib/commands/quality.js +211 -0
- package/lib/commands/remove.js +82 -0
- package/lib/commands/run.js +14 -0
- package/lib/commands/search.js +143 -0
- package/lib/commands/setup.js +92 -0
- package/lib/commands/skills.js +863 -0
- package/lib/commands/token-rotate.js +25 -0
- package/lib/commands/whoami.js +129 -0
- package/lib/config.js +240 -0
- package/lib/constants.js +190 -0
- package/lib/ecosystem.js +501 -0
- package/lib/editors.js +215 -0
- package/lib/import-rewriter.js +364 -0
- package/lib/install-targets/mcp-server.js +245 -0
- package/lib/install-targets/vscode-extension.js +178 -0
- package/lib/install-targets.js +82 -0
- package/lib/integrity.js +179 -0
- package/lib/lpm-config-prompts.js +102 -0
- package/lib/lpm-config.js +408 -0
- package/lib/project-utils.js +152 -0
- package/lib/quality/checks.js +654 -0
- package/lib/quality/display.js +139 -0
- package/lib/quality/score.js +115 -0
- package/lib/quality/swift-checks.js +447 -0
- package/lib/safe-path.js +180 -0
- package/lib/secure-store.js +288 -0
- package/lib/swift-project.js +637 -0
- package/lib/ui.js +40 -0
- package/package.json +74 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Command
|
|
3
|
+
*
|
|
4
|
+
* Scan project dependencies for known security vulnerabilities.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* lpm audit [options]
|
|
8
|
+
* lpm audit fix
|
|
9
|
+
*
|
|
10
|
+
* Options:
|
|
11
|
+
* --json Output in JSON format
|
|
12
|
+
* --level Minimum severity to report (low, moderate, high, critical)
|
|
13
|
+
*
|
|
14
|
+
* @module cli/lib/commands/audit
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
18
|
+
import { join } from "node:path"
|
|
19
|
+
import chalk from "chalk"
|
|
20
|
+
import ora from "ora"
|
|
21
|
+
import { post } from "../api.js"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Severity levels in order of priority.
|
|
25
|
+
*/
|
|
26
|
+
const SEVERITY_ORDER = ["critical", "high", "moderate", "low", "info"]
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Severity colors.
|
|
30
|
+
*/
|
|
31
|
+
const SEVERITY_COLORS = {
|
|
32
|
+
critical: chalk.bgRed.white,
|
|
33
|
+
high: chalk.red,
|
|
34
|
+
moderate: chalk.yellow,
|
|
35
|
+
low: chalk.blue,
|
|
36
|
+
info: chalk.dim,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read and parse package.json from current directory.
|
|
41
|
+
* @returns {{ dependencies: Record<string, string>, devDependencies: Record<string, string> } | null}
|
|
42
|
+
*/
|
|
43
|
+
function readPackageJson() {
|
|
44
|
+
const packageJsonPath = join(process.cwd(), "package.json")
|
|
45
|
+
|
|
46
|
+
if (!existsSync(packageJsonPath)) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const content = readFileSync(packageJsonPath, "utf8")
|
|
52
|
+
return JSON.parse(content)
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read and parse package-lock.json for exact versions.
|
|
60
|
+
* @returns {Record<string, { version: string, resolved?: string }> | null}
|
|
61
|
+
*/
|
|
62
|
+
function readPackageLock() {
|
|
63
|
+
const lockPath = join(process.cwd(), "package-lock.json")
|
|
64
|
+
|
|
65
|
+
if (!existsSync(lockPath)) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(lockPath, "utf8")
|
|
71
|
+
const lock = JSON.parse(content)
|
|
72
|
+
return lock.packages || lock.dependencies || null
|
|
73
|
+
} catch {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format vulnerability count by severity.
|
|
80
|
+
* @param {Object} counts
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function formatVulnCounts(counts) {
|
|
84
|
+
const parts = []
|
|
85
|
+
|
|
86
|
+
for (const severity of SEVERITY_ORDER) {
|
|
87
|
+
const count = counts[severity] || 0
|
|
88
|
+
if (count > 0) {
|
|
89
|
+
const color = SEVERITY_COLORS[severity] || chalk.dim
|
|
90
|
+
parts.push(color(`${count} ${severity}`))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return parts.join(", ") || chalk.green("0 vulnerabilities")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Execute the audit command.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} [action] - Optional action ('fix')
|
|
101
|
+
* @param {Object} options - Command options
|
|
102
|
+
* @param {boolean} [options.json] - Output as JSON
|
|
103
|
+
* @param {string} [options.level] - Minimum severity level
|
|
104
|
+
*/
|
|
105
|
+
export async function audit(action, options = {}) {
|
|
106
|
+
const spinner = ora("Reading dependencies...").start()
|
|
107
|
+
|
|
108
|
+
// Read package.json
|
|
109
|
+
const packageJson = readPackageJson()
|
|
110
|
+
|
|
111
|
+
if (!packageJson) {
|
|
112
|
+
spinner.fail(chalk.red("No package.json found in current directory."))
|
|
113
|
+
process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const dependencies = {
|
|
117
|
+
...(packageJson.dependencies || {}),
|
|
118
|
+
...(packageJson.devDependencies || {}),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const depCount = Object.keys(dependencies).length
|
|
122
|
+
|
|
123
|
+
if (depCount === 0) {
|
|
124
|
+
spinner.succeed(chalk.green("No dependencies to audit."))
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Try to get exact versions from lock file
|
|
129
|
+
const lockData = readPackageLock()
|
|
130
|
+
|
|
131
|
+
// Build dependency list for API
|
|
132
|
+
const depList = Object.entries(dependencies).map(([name, version]) => {
|
|
133
|
+
// Try to get exact version from lock
|
|
134
|
+
let exactVersion = version
|
|
135
|
+
if (lockData) {
|
|
136
|
+
const lockEntry = lockData[name] || lockData[`node_modules/${name}`]
|
|
137
|
+
if (lockEntry?.version) {
|
|
138
|
+
exactVersion = lockEntry.version
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { name, version: exactVersion }
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
spinner.text = `Auditing ${depCount} dependencies...`
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const response = await post(
|
|
148
|
+
"/audit",
|
|
149
|
+
{ dependencies: depList },
|
|
150
|
+
{
|
|
151
|
+
onRetry: (attempt, max) => {
|
|
152
|
+
spinner.text = `Auditing (retry ${attempt}/${max})...`
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
const data = await response.json().catch(() => ({}))
|
|
159
|
+
throw new Error(data.error || "Audit request failed.")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const data = await response.json()
|
|
163
|
+
const vulnerabilities = data.vulnerabilities || []
|
|
164
|
+
|
|
165
|
+
spinner.stop()
|
|
166
|
+
|
|
167
|
+
// Filter by severity level if specified
|
|
168
|
+
let filteredVulns = vulnerabilities
|
|
169
|
+
if (options.level) {
|
|
170
|
+
const levelIndex = SEVERITY_ORDER.indexOf(options.level)
|
|
171
|
+
if (levelIndex !== -1) {
|
|
172
|
+
filteredVulns = vulnerabilities.filter(v => {
|
|
173
|
+
const vIndex = SEVERITY_ORDER.indexOf(v.severity)
|
|
174
|
+
return vIndex !== -1 && vIndex <= levelIndex
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// JSON output
|
|
180
|
+
if (options.json) {
|
|
181
|
+
console.log(
|
|
182
|
+
JSON.stringify(
|
|
183
|
+
{
|
|
184
|
+
scanned: depCount,
|
|
185
|
+
vulnerabilities: filteredVulns,
|
|
186
|
+
counts: data.counts || {},
|
|
187
|
+
},
|
|
188
|
+
null,
|
|
189
|
+
2,
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// No vulnerabilities
|
|
196
|
+
if (filteredVulns.length === 0) {
|
|
197
|
+
console.log(
|
|
198
|
+
chalk.green(
|
|
199
|
+
`\n✓ No vulnerabilities found in ${depCount} dependencies.\n`,
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Display vulnerabilities
|
|
206
|
+
console.log(
|
|
207
|
+
chalk.bold(
|
|
208
|
+
`\nFound ${formatVulnCounts(data.counts || {})} in ${depCount} dependencies.\n`,
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// Group by package
|
|
213
|
+
const byPackage = {}
|
|
214
|
+
for (const vuln of filteredVulns) {
|
|
215
|
+
const key = vuln.package
|
|
216
|
+
if (!byPackage[key]) {
|
|
217
|
+
byPackage[key] = []
|
|
218
|
+
}
|
|
219
|
+
byPackage[key].push(vuln)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Display each package
|
|
223
|
+
for (const [pkg, vulns] of Object.entries(byPackage)) {
|
|
224
|
+
console.log(chalk.cyan.bold(pkg))
|
|
225
|
+
|
|
226
|
+
for (const vuln of vulns) {
|
|
227
|
+
const severityColor = SEVERITY_COLORS[vuln.severity] || chalk.dim
|
|
228
|
+
const severity = severityColor(vuln.severity.padEnd(10))
|
|
229
|
+
const title = vuln.title || vuln.id || "Unknown vulnerability"
|
|
230
|
+
|
|
231
|
+
console.log(` ${severity} ${title}`)
|
|
232
|
+
|
|
233
|
+
if (vuln.vulnerable_versions) {
|
|
234
|
+
console.log(chalk.dim(` Vulnerable: ${vuln.vulnerable_versions}`))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (vuln.patched_versions) {
|
|
238
|
+
console.log(
|
|
239
|
+
chalk.green(` Fix: Upgrade to ${vuln.patched_versions}`),
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (vuln.url) {
|
|
244
|
+
console.log(chalk.dim(` More info: ${vuln.url}`))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log("")
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fix suggestion
|
|
252
|
+
if (action !== "fix") {
|
|
253
|
+
console.log(chalk.dim("Run `lpm audit fix` to attempt automatic fixes."))
|
|
254
|
+
console.log("")
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Handle fix action
|
|
258
|
+
if (action === "fix") {
|
|
259
|
+
console.log(chalk.yellow("Automatic fix is not yet implemented."))
|
|
260
|
+
console.log(
|
|
261
|
+
chalk.dim(
|
|
262
|
+
"Please update vulnerable packages manually based on the recommendations above.",
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
console.log("")
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Exit with error code if vulnerabilities found
|
|
269
|
+
if (
|
|
270
|
+
filteredVulns.some(
|
|
271
|
+
v => v.severity === "critical" || v.severity === "high",
|
|
272
|
+
)
|
|
273
|
+
) {
|
|
274
|
+
process.exit(1)
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
spinner.fail(chalk.red("Audit failed."))
|
|
278
|
+
console.error(chalk.red(` ${error.message}`))
|
|
279
|
+
process.exit(1)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export default audit
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Command
|
|
3
|
+
*
|
|
4
|
+
* Manage the local package cache.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* lpm cache clean Clear all cached packages
|
|
8
|
+
* lpm cache list Show cached packages with sizes
|
|
9
|
+
* lpm cache path Show cache directory location
|
|
10
|
+
*
|
|
11
|
+
* @module cli/lib/commands/cache
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, rmSync, statSync } from "node:fs"
|
|
15
|
+
import { homedir } from "node:os"
|
|
16
|
+
import { join } from "node:path"
|
|
17
|
+
import chalk from "chalk"
|
|
18
|
+
import ora from "ora"
|
|
19
|
+
import { CACHE_DIR_NAME } from "../constants.js"
|
|
20
|
+
|
|
21
|
+
/** Full path to cache directory */
|
|
22
|
+
const CACHE_DIR = join(homedir(), CACHE_DIR_NAME)
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format bytes to human-readable size.
|
|
26
|
+
* @param {number} bytes
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function formatSize(bytes) {
|
|
30
|
+
if (bytes === 0) return "0 B"
|
|
31
|
+
|
|
32
|
+
const units = ["B", "KB", "MB", "GB"]
|
|
33
|
+
const k = 1024
|
|
34
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
35
|
+
|
|
36
|
+
return `${(bytes / k ** i).toFixed(2)} ${units[i]}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the size of a directory recursively.
|
|
41
|
+
* @param {string} dirPath
|
|
42
|
+
* @returns {number} - Total size in bytes
|
|
43
|
+
*/
|
|
44
|
+
function getDirectorySize(dirPath) {
|
|
45
|
+
if (!existsSync(dirPath)) return 0
|
|
46
|
+
|
|
47
|
+
let totalSize = 0
|
|
48
|
+
|
|
49
|
+
const entries = readdirSync(dirPath, { withFileTypes: true })
|
|
50
|
+
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const fullPath = join(dirPath, entry.name)
|
|
53
|
+
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
totalSize += getDirectorySize(fullPath)
|
|
56
|
+
} else if (entry.isFile()) {
|
|
57
|
+
totalSize += statSync(fullPath).size
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return totalSize
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* List cached packages with their sizes.
|
|
66
|
+
* @param {string} dirPath
|
|
67
|
+
* @param {string} [prefix='']
|
|
68
|
+
* @returns {{ name: string, size: number }[]}
|
|
69
|
+
*/
|
|
70
|
+
function listCacheEntries(dirPath, prefix = "") {
|
|
71
|
+
if (!existsSync(dirPath)) return []
|
|
72
|
+
|
|
73
|
+
const entries = []
|
|
74
|
+
const items = readdirSync(dirPath, { withFileTypes: true })
|
|
75
|
+
|
|
76
|
+
for (const item of items) {
|
|
77
|
+
const fullPath = join(dirPath, item.name)
|
|
78
|
+
const name = prefix ? `${prefix}/${item.name}` : item.name
|
|
79
|
+
|
|
80
|
+
if (item.isDirectory()) {
|
|
81
|
+
// Check if this is a package directory (has .tgz files)
|
|
82
|
+
const contents = readdirSync(fullPath)
|
|
83
|
+
const hasTarballs = contents.some(f => f.endsWith(".tgz"))
|
|
84
|
+
|
|
85
|
+
if (hasTarballs) {
|
|
86
|
+
entries.push({
|
|
87
|
+
name,
|
|
88
|
+
size: getDirectorySize(fullPath),
|
|
89
|
+
versions: contents.filter(f => f.endsWith(".tgz")).length,
|
|
90
|
+
})
|
|
91
|
+
} else {
|
|
92
|
+
// Recurse into scope directories
|
|
93
|
+
entries.push(...listCacheEntries(fullPath, name))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return entries
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clear all cached packages.
|
|
103
|
+
*/
|
|
104
|
+
export async function clearCache() {
|
|
105
|
+
const spinner = ora("Clearing cache...").start()
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (!existsSync(CACHE_DIR)) {
|
|
109
|
+
spinner.info("Cache is already empty.")
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const sizeBefore = getDirectorySize(CACHE_DIR)
|
|
114
|
+
|
|
115
|
+
rmSync(CACHE_DIR, { recursive: true, force: true })
|
|
116
|
+
|
|
117
|
+
spinner.succeed(
|
|
118
|
+
chalk.green(`Cleared ${formatSize(sizeBefore)} from cache.`),
|
|
119
|
+
)
|
|
120
|
+
} catch (error) {
|
|
121
|
+
spinner.fail(chalk.red("Failed to clear cache."))
|
|
122
|
+
console.error(chalk.red(` ${error.message}`))
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List cached packages.
|
|
129
|
+
*/
|
|
130
|
+
function listCachedPackages() {
|
|
131
|
+
console.log(chalk.bold("\nCached Packages:\n"))
|
|
132
|
+
|
|
133
|
+
if (!existsSync(CACHE_DIR)) {
|
|
134
|
+
console.log(chalk.dim(" No packages cached."))
|
|
135
|
+
console.log("")
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const entries = listCacheEntries(CACHE_DIR)
|
|
140
|
+
|
|
141
|
+
if (entries.length === 0) {
|
|
142
|
+
console.log(chalk.dim(" No packages cached."))
|
|
143
|
+
console.log("")
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Sort by size descending
|
|
148
|
+
entries.sort((a, b) => b.size - a.size)
|
|
149
|
+
|
|
150
|
+
const totalSize = entries.reduce((sum, e) => sum + e.size, 0)
|
|
151
|
+
|
|
152
|
+
// Display each package
|
|
153
|
+
const maxNameLength = Math.max(...entries.map(e => e.name.length))
|
|
154
|
+
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
const paddedName = entry.name.padEnd(maxNameLength)
|
|
157
|
+
const size = formatSize(entry.size).padStart(10)
|
|
158
|
+
const versions = chalk.dim(
|
|
159
|
+
`(${entry.versions} version${entry.versions > 1 ? "s" : ""})`,
|
|
160
|
+
)
|
|
161
|
+
console.log(` ${chalk.cyan(paddedName)} ${size} ${versions}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log("")
|
|
165
|
+
console.log(
|
|
166
|
+
chalk.bold(
|
|
167
|
+
` Total: ${formatSize(totalSize)} in ${entries.length} package${entries.length > 1 ? "s" : ""}`,
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
console.log("")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Show cache directory path.
|
|
175
|
+
*/
|
|
176
|
+
function showCachePath() {
|
|
177
|
+
console.log(CACHE_DIR)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Execute the cache command.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} action - The action to perform (clean, list, path)
|
|
184
|
+
*/
|
|
185
|
+
export async function cache(action) {
|
|
186
|
+
switch (action) {
|
|
187
|
+
case "clean":
|
|
188
|
+
case "clear":
|
|
189
|
+
await clearCache()
|
|
190
|
+
break
|
|
191
|
+
|
|
192
|
+
case "list":
|
|
193
|
+
case "ls":
|
|
194
|
+
listCachedPackages()
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
case "path":
|
|
198
|
+
case "dir":
|
|
199
|
+
showCachePath()
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
default:
|
|
203
|
+
console.error(chalk.red(`Unknown action: ${action}`))
|
|
204
|
+
console.log(chalk.dim("Available actions: clean, list, path"))
|
|
205
|
+
process.exit(1)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export default cache
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import chalk from "chalk"
|
|
2
|
+
import ora from "ora"
|
|
3
|
+
import { get } from "../api.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate the package name format.
|
|
7
|
+
* Must be owner.package-name (no @lpm.dev/ prefix needed, but accepted).
|
|
8
|
+
* @param {string} input
|
|
9
|
+
* @returns {{ owner: string, name: string } | null}
|
|
10
|
+
*/
|
|
11
|
+
function parseName(input) {
|
|
12
|
+
let cleaned = input
|
|
13
|
+
if (cleaned.startsWith("@lpm.dev/")) {
|
|
14
|
+
cleaned = cleaned.replace("@lpm.dev/", "")
|
|
15
|
+
}
|
|
16
|
+
const dotIndex = cleaned.indexOf(".")
|
|
17
|
+
if (dotIndex === -1 || dotIndex === 0 || dotIndex === cleaned.length - 1) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
owner: cleaned.substring(0, dotIndex),
|
|
22
|
+
name: cleaned.substring(dotIndex + 1),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a package name is available on the LPM registry.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} nameInput - Package name (e.g., "owner.package-name" or "@lpm.dev/owner.package-name")
|
|
30
|
+
* @param {Object} [options]
|
|
31
|
+
* @param {boolean} [options.json] - Output as JSON
|
|
32
|
+
*/
|
|
33
|
+
export async function checkName(nameInput, options = {}) {
|
|
34
|
+
if (!nameInput || nameInput.trim() === "") {
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.log(JSON.stringify({ error: "Package name required." }))
|
|
37
|
+
} else {
|
|
38
|
+
console.error(chalk.red("Error: Package name required."))
|
|
39
|
+
console.log(chalk.dim("Usage: lpm check-name owner.package-name"))
|
|
40
|
+
}
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const parsed = parseName(nameInput.trim())
|
|
45
|
+
if (!parsed) {
|
|
46
|
+
if (options.json) {
|
|
47
|
+
console.log(
|
|
48
|
+
JSON.stringify({
|
|
49
|
+
error: "Invalid package name format. Use: owner.package-name",
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
console.error(chalk.red("Error: Invalid package name format."))
|
|
54
|
+
console.log(chalk.dim("Expected: owner.package-name"))
|
|
55
|
+
console.log(chalk.dim("Example: lpm check-name tolgaergin.my-utils"))
|
|
56
|
+
}
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fullName = `@lpm.dev/${parsed.owner}.${parsed.name}`
|
|
61
|
+
const spinner = options.json
|
|
62
|
+
? null
|
|
63
|
+
: ora(`Checking availability of ${fullName}...`).start()
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const queryName = `${parsed.owner}.${parsed.name}`
|
|
67
|
+
const response = await get(
|
|
68
|
+
`/check-name?name=${encodeURIComponent(queryName)}`,
|
|
69
|
+
{
|
|
70
|
+
onRetry: spinner
|
|
71
|
+
? (attempt, max) => {
|
|
72
|
+
spinner.text = `Checking (retry ${attempt}/${max})...`
|
|
73
|
+
}
|
|
74
|
+
: undefined,
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const data = await response.json().catch(() => ({}))
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(data.error || `Unexpected response: ${response.status}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (options.json) {
|
|
85
|
+
console.log(JSON.stringify(data, null, 2))
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (data.available) {
|
|
90
|
+
spinner.succeed(chalk.green(`${fullName} is available!`))
|
|
91
|
+
if (!data.ownerExists) {
|
|
92
|
+
console.log(
|
|
93
|
+
chalk.yellow(
|
|
94
|
+
` Note: Owner "${parsed.owner}" doesn't exist yet. You'll need to create this account first.`,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
spinner.fail(chalk.red(`${fullName} is already taken.`))
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (spinner) spinner.fail(chalk.red("Failed to check name availability."))
|
|
103
|
+
if (options.json) {
|
|
104
|
+
console.log(JSON.stringify({ error: error.message }))
|
|
105
|
+
} else {
|
|
106
|
+
console.error(chalk.red(` ${error.message}`))
|
|
107
|
+
}
|
|
108
|
+
process.exit(1)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default checkName
|