@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/quality.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk"
|
|
2
|
-
import ora from "ora"
|
|
3
|
-
import { get } from "../api.js"
|
|
4
|
-
|
|
5
|
-
const TIER_COLORS = {
|
|
6
|
-
excellent: chalk.green,
|
|
7
|
-
good: chalk.blue,
|
|
8
|
-
fair: chalk.yellow,
|
|
9
|
-
"needs-work": chalk.gray,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const TIER_LABELS = {
|
|
13
|
-
excellent: "Excellent",
|
|
14
|
-
good: "Good",
|
|
15
|
-
fair: "Fair",
|
|
16
|
-
"needs-work": "Needs Work",
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const CATEGORY_LABELS = {
|
|
20
|
-
documentation: "Documentation",
|
|
21
|
-
code: "Code Quality",
|
|
22
|
-
testing: "Testing",
|
|
23
|
-
health: "Package Health",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function progressBar(value, max, width = 18) {
|
|
27
|
-
const ratio = Math.min(value / max, 1)
|
|
28
|
-
const filled = Math.round(ratio * width)
|
|
29
|
-
const empty = width - filled
|
|
30
|
-
return chalk.cyan("█".repeat(filled)) + chalk.gray("░".repeat(empty))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Parse package name input.
|
|
35
|
-
* Accepts: "owner.pkg", "@lpm.dev/owner.pkg"
|
|
36
|
-
* @param {string} input
|
|
37
|
-
* @returns {string|null} cleaned name as "owner.pkg" or null if invalid
|
|
38
|
-
*/
|
|
39
|
-
function parseName(input) {
|
|
40
|
-
let cleaned = input
|
|
41
|
-
if (cleaned.startsWith("@lpm.dev/")) {
|
|
42
|
-
cleaned = cleaned.replace("@lpm.dev/", "")
|
|
43
|
-
}
|
|
44
|
-
const dotIndex = cleaned.indexOf(".")
|
|
45
|
-
if (dotIndex === -1 || dotIndex === 0 || dotIndex === cleaned.length - 1) {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
return cleaned
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const TIPS = {
|
|
52
|
-
"has-readme": "Add a README.md with at least 100 characters",
|
|
53
|
-
"readme-install": "Add an install/getting started section to your README",
|
|
54
|
-
"readme-usage": "Add usage examples with code blocks to your README",
|
|
55
|
-
"readme-api": "Add an API/reference section to your README",
|
|
56
|
-
"has-changelog": "Add a CHANGELOG.md file",
|
|
57
|
-
"has-license": "Add a LICENSE file",
|
|
58
|
-
"has-types": 'Add TypeScript types ("types" field or .d.ts files)',
|
|
59
|
-
"intellisense-coverage": "Add .d.ts type definitions or JSDoc comments",
|
|
60
|
-
"esm-exports": 'Add "type": "module" or "exports" to package.json',
|
|
61
|
-
"tree-shakable": 'Add "sideEffects": false to package.json',
|
|
62
|
-
"no-eval": "Remove eval() and new Function() usage",
|
|
63
|
-
"has-engines": 'Add "engines": { "node": ">=18" } to package.json',
|
|
64
|
-
"has-exports-map": 'Add an "exports" map to package.json',
|
|
65
|
-
"small-deps": "Reduce the number of production dependencies",
|
|
66
|
-
"source-maps": "Include .js.map source maps for easier debugging",
|
|
67
|
-
"has-test-files": "Add test files (*.test.js, *.spec.js)",
|
|
68
|
-
"has-test-script": "Add a test script to package.json",
|
|
69
|
-
"has-description": "Add a description (>10 chars) to package.json",
|
|
70
|
-
"has-keywords": "Add keywords to package.json",
|
|
71
|
-
"has-repository": "Add a repository field to package.json",
|
|
72
|
-
"has-homepage": "Add a homepage field to package.json",
|
|
73
|
-
"reasonable-size": "Reduce unpacked size (check for unnecessary files)",
|
|
74
|
-
"no-vulnerabilities": "Fix known vulnerabilities in dependencies",
|
|
75
|
-
"maintenance-health": "Publish updates regularly (within 90 days)",
|
|
76
|
-
"semver-consistency": "Use valid semantic versioning (major.minor.patch)",
|
|
77
|
-
"author-verified": "Link your GitHub or LinkedIn in your profile settings",
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Fetch and display the server-side quality report for a package.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} nameInput - Package name (e.g., "owner.pkg" or "@lpm.dev/owner.pkg")
|
|
84
|
-
* @param {Object} [options]
|
|
85
|
-
* @param {boolean} [options.json] - Output as JSON
|
|
86
|
-
*/
|
|
87
|
-
export async function quality(nameInput, options = {}) {
|
|
88
|
-
if (!nameInput || nameInput.trim() === "") {
|
|
89
|
-
if (options.json) {
|
|
90
|
-
console.log(JSON.stringify({ error: "Package name required." }))
|
|
91
|
-
} else {
|
|
92
|
-
console.error(chalk.red("Error: Package name required."))
|
|
93
|
-
console.log(chalk.dim("Usage: lpm quality owner.package-name"))
|
|
94
|
-
}
|
|
95
|
-
process.exit(1)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const cleaned = parseName(nameInput.trim())
|
|
99
|
-
if (!cleaned) {
|
|
100
|
-
if (options.json) {
|
|
101
|
-
console.log(
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
error: "Invalid package name format. Use: owner.package-name",
|
|
104
|
-
}),
|
|
105
|
-
)
|
|
106
|
-
} else {
|
|
107
|
-
console.error(chalk.red("Error: Invalid package name format."))
|
|
108
|
-
console.log(chalk.dim("Expected: owner.package-name"))
|
|
109
|
-
}
|
|
110
|
-
process.exit(1)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const fullName = `@lpm.dev/${cleaned}`
|
|
114
|
-
const spinner = options.json
|
|
115
|
-
? null
|
|
116
|
-
: ora(`Fetching quality report for ${fullName}...`).start()
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const response = await get(`/quality?name=${encodeURIComponent(cleaned)}`, {
|
|
120
|
-
onRetry: spinner
|
|
121
|
-
? (attempt, max) => {
|
|
122
|
-
spinner.text = `Fetching (retry ${attempt}/${max})...`
|
|
123
|
-
}
|
|
124
|
-
: undefined,
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
const data = await response.json().catch(() => ({}))
|
|
128
|
-
|
|
129
|
-
if (!response.ok) {
|
|
130
|
-
throw new Error(data.error || `Request failed: ${response.status}`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (options.json) {
|
|
134
|
-
console.log(JSON.stringify(data, null, 2))
|
|
135
|
-
return
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (spinner) spinner.stop()
|
|
139
|
-
|
|
140
|
-
// No quality data
|
|
141
|
-
if (data.score === null || data.score === undefined) {
|
|
142
|
-
console.log("")
|
|
143
|
-
console.log(chalk.yellow(` ${fullName}: No quality data available.`))
|
|
144
|
-
console.log(
|
|
145
|
-
chalk.dim(" Publish with lpm CLI v0.16+ to generate quality scores."),
|
|
146
|
-
)
|
|
147
|
-
console.log("")
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Display quality report
|
|
152
|
-
const tier = data.tier || "needs-work"
|
|
153
|
-
const tierColor = TIER_COLORS[tier] || chalk.white
|
|
154
|
-
const tierLabel = TIER_LABELS[tier] || tier
|
|
155
|
-
|
|
156
|
-
console.log("")
|
|
157
|
-
console.log(` ${chalk.bold(fullName)}`)
|
|
158
|
-
console.log(
|
|
159
|
-
` ${chalk.bold("Quality Score:")} ${tierColor(`${data.score}/${data.maxScore}`)} ${tierColor(`(${tierLabel})`)}`,
|
|
160
|
-
)
|
|
161
|
-
console.log("")
|
|
162
|
-
|
|
163
|
-
// Category bars
|
|
164
|
-
if (data.categories) {
|
|
165
|
-
for (const [cat, { score: catScore, max }] of Object.entries(
|
|
166
|
-
data.categories,
|
|
167
|
-
)) {
|
|
168
|
-
const label = (CATEGORY_LABELS[cat] || cat).padEnd(16)
|
|
169
|
-
const bar = progressBar(catScore, max)
|
|
170
|
-
console.log(` ${label}${bar} ${catScore}/${max}`)
|
|
171
|
-
}
|
|
172
|
-
console.log("")
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Individual checks
|
|
176
|
-
if (data.checks && data.checks.length > 0) {
|
|
177
|
-
for (const check of data.checks) {
|
|
178
|
-
const icon = check.passed ? chalk.green("✓") : chalk.red("✗")
|
|
179
|
-
const label = check.passed ? check.label : chalk.dim(check.label)
|
|
180
|
-
const points = check.passed
|
|
181
|
-
? chalk.dim(` ${check.points}/${check.max_points}`)
|
|
182
|
-
: chalk.dim(` 0/${check.max_points}`)
|
|
183
|
-
const detail = check.detail ? chalk.dim(` — ${check.detail}`) : ""
|
|
184
|
-
console.log(` ${icon} ${label}${points}${detail}`)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Tips for failed checks
|
|
188
|
-
const failed = data.checks.filter(c => !c.passed)
|
|
189
|
-
if (failed.length > 0) {
|
|
190
|
-
console.log("")
|
|
191
|
-
const tips = failed.slice(0, 3)
|
|
192
|
-
for (const check of tips) {
|
|
193
|
-
const tip = TIPS[check.id] || `Improve: ${check.id}`
|
|
194
|
-
console.log(chalk.dim(` Tip: ${tip} (+${check.max_points} points)`))
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
console.log("")
|
|
200
|
-
} catch (error) {
|
|
201
|
-
if (spinner) spinner.fail(chalk.red("Failed to fetch quality report."))
|
|
202
|
-
if (options.json) {
|
|
203
|
-
console.log(JSON.stringify({ error: error.message }))
|
|
204
|
-
} else {
|
|
205
|
-
console.error(chalk.red(` ${error.message}`))
|
|
206
|
-
}
|
|
207
|
-
process.exit(1)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export default quality
|
package/lib/commands/remove.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import * as p from "@clack/prompts"
|
|
2
|
-
import chalk from "chalk"
|
|
3
|
-
import { getRegistryUrl, getToken } from "../config.js"
|
|
4
|
-
import { getHandler, hasCustomHandler } from "../install-targets.js"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Remove a previously added package.
|
|
8
|
-
*
|
|
9
|
-
* For packages with custom install handlers (e.g., MCP servers), this
|
|
10
|
-
* delegates to the handler's remove function. For standard source packages,
|
|
11
|
-
* it advises manual removal since files were copied into the project.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} pkgName - Package name (e.g., "@lpm.dev/owner.my-pkg")
|
|
14
|
-
* @param {object} options - CLI options
|
|
15
|
-
*/
|
|
16
|
-
export async function remove(pkgName, _options) {
|
|
17
|
-
p.intro(chalk.bgCyan(chalk.black(" lpm remove ")))
|
|
18
|
-
|
|
19
|
-
// 1. Auth Check
|
|
20
|
-
const token = await getToken()
|
|
21
|
-
if (!token) {
|
|
22
|
-
p.log.error("Not logged in. Run `lpm login` first.")
|
|
23
|
-
p.outro("")
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 2. Normalize package name
|
|
28
|
-
const name = pkgName.startsWith("@lpm.dev/") ? pkgName : `@lpm.dev/${pkgName}`
|
|
29
|
-
|
|
30
|
-
// 3. Fetch package metadata to determine type
|
|
31
|
-
const baseRegistryUrl = getRegistryUrl()
|
|
32
|
-
const registryUrl = baseRegistryUrl.endsWith("/api/registry")
|
|
33
|
-
? baseRegistryUrl
|
|
34
|
-
: `${baseRegistryUrl}/api/registry`
|
|
35
|
-
const encodedName = name.replace("/", "%2f")
|
|
36
|
-
|
|
37
|
-
let meta
|
|
38
|
-
try {
|
|
39
|
-
const res = await fetch(`${registryUrl}/${encodedName}`, {
|
|
40
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
if (res.status === 404) {
|
|
45
|
-
p.log.error(`Package '${name}' not found.`)
|
|
46
|
-
p.outro("")
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
throw new Error(res.statusText)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
meta = await res.json()
|
|
53
|
-
} catch (err) {
|
|
54
|
-
p.log.error(`Failed to fetch package info: ${err.message}`)
|
|
55
|
-
p.outro("")
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 4. Determine package type from latest version's lpmConfig
|
|
60
|
-
const latestVersion = meta["dist-tags"]?.latest
|
|
61
|
-
const versionData = latestVersion ? meta.versions[latestVersion] : null
|
|
62
|
-
const packageType = versionData?.lpmConfig?.type || meta.packageType
|
|
63
|
-
|
|
64
|
-
// 5. Route to type-specific handler or show manual instructions
|
|
65
|
-
if (packageType && hasCustomHandler(packageType)) {
|
|
66
|
-
const handler = getHandler(packageType)
|
|
67
|
-
const result = await handler.remove({ name })
|
|
68
|
-
|
|
69
|
-
if (result.success) {
|
|
70
|
-
p.log.success(result.message)
|
|
71
|
-
} else {
|
|
72
|
-
p.log.error(result.message)
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
p.log.info(
|
|
76
|
-
`${chalk.cyan(name)} was installed as source files copied into your project.`,
|
|
77
|
-
)
|
|
78
|
-
p.log.info("Remove the files manually from your project directory.")
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
p.outro("")
|
|
82
|
-
}
|
package/lib/commands/run.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process"
|
|
2
|
-
import { log } from "../ui.js"
|
|
3
|
-
|
|
4
|
-
export async function run(script, _options, command) {
|
|
5
|
-
const args = ["run", script, ...command.args]
|
|
6
|
-
|
|
7
|
-
const child = spawn("npm", args, { stdio: "inherit" })
|
|
8
|
-
|
|
9
|
-
child.on("close", code => process.exit(code))
|
|
10
|
-
child.on("error", err => {
|
|
11
|
-
log.error(`Failed to start npm: ${err.message}`)
|
|
12
|
-
process.exit(1)
|
|
13
|
-
})
|
|
14
|
-
}
|
package/lib/commands/search.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Search Command
|
|
3
|
-
*
|
|
4
|
-
* Search for packages using hybrid keyword + semantic search.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* lpm search <query> [options]
|
|
8
|
-
*
|
|
9
|
-
* Options:
|
|
10
|
-
* --limit <n> Maximum number of results (default: 20)
|
|
11
|
-
* --json Output in JSON format
|
|
12
|
-
*
|
|
13
|
-
* @module cli/lib/commands/search
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import chalk from "chalk"
|
|
17
|
-
import ora from "ora"
|
|
18
|
-
import { searchGet } from "../api.js"
|
|
19
|
-
import { DEFAULT_PAGE_LIMIT } from "../constants.js"
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Format downloads count.
|
|
23
|
-
* @param {number} count
|
|
24
|
-
* @returns {string}
|
|
25
|
-
*/
|
|
26
|
-
function formatDownloads(count) {
|
|
27
|
-
if (count >= 1000000) {
|
|
28
|
-
return `${(count / 1000000).toFixed(1)}M`
|
|
29
|
-
}
|
|
30
|
-
if (count >= 1000) {
|
|
31
|
-
return `${(count / 1000).toFixed(1)}K`
|
|
32
|
-
}
|
|
33
|
-
return String(count)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Execute the search command.
|
|
38
|
-
*
|
|
39
|
-
* @param {string} query - Search query
|
|
40
|
-
* @param {Object} options - Command options
|
|
41
|
-
* @param {number} [options.limit] - Maximum results
|
|
42
|
-
* @param {boolean} [options.json] - Output as JSON
|
|
43
|
-
*/
|
|
44
|
-
export async function search(query, options = {}) {
|
|
45
|
-
if (!query || query.trim() === "") {
|
|
46
|
-
console.error(chalk.red("Error: Search query required."))
|
|
47
|
-
console.log(chalk.dim("Usage: lpm search <query>"))
|
|
48
|
-
process.exit(1)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const limit = options.limit || DEFAULT_PAGE_LIMIT
|
|
52
|
-
const spinner = ora(`Searching for "${query}"...`).start()
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const params = new URLSearchParams({
|
|
56
|
-
q: query,
|
|
57
|
-
mode: "semantic",
|
|
58
|
-
limit: String(limit),
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const response = await searchGet(`/packages?${params.toString()}`, {
|
|
62
|
-
onRetry: (attempt, max) => {
|
|
63
|
-
spinner.text = `Searching (retry ${attempt}/${max})...`
|
|
64
|
-
},
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
const data = await response.json().catch(() => ({}))
|
|
69
|
-
throw new Error(
|
|
70
|
-
data.error || `Search failed with status ${response.status}`,
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const data = await response.json()
|
|
75
|
-
const packages = data.packages || []
|
|
76
|
-
|
|
77
|
-
spinner.stop()
|
|
78
|
-
|
|
79
|
-
if (options.json) {
|
|
80
|
-
console.log(JSON.stringify(packages, null, 2))
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (packages.length === 0) {
|
|
85
|
-
console.log(chalk.yellow(`\nNo packages found for "${query}".`))
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
console.log(
|
|
90
|
-
chalk.bold(
|
|
91
|
-
`\nFound ${packages.length} package${packages.length > 1 ? "s" : ""}:\n`,
|
|
92
|
-
),
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
// Calculate column widths (use LPM format: @lpm.dev/owner.name)
|
|
96
|
-
const maxNameLength = Math.max(
|
|
97
|
-
...packages.map(p => {
|
|
98
|
-
const owner = p.ownerSlug || p.owner
|
|
99
|
-
return (owner ? `@lpm.dev/${owner}.${p.name}` : p.name).length
|
|
100
|
-
}),
|
|
101
|
-
10,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
for (const pkg of packages) {
|
|
105
|
-
const owner = pkg.ownerSlug || pkg.owner
|
|
106
|
-
const fullName = owner ? `@lpm.dev/${owner}.${pkg.name}` : pkg.name
|
|
107
|
-
const paddedName = fullName.padEnd(maxNameLength)
|
|
108
|
-
const version = chalk.dim(
|
|
109
|
-
`v${pkg.latestVersion || pkg.version || "unknown"}`.padEnd(12),
|
|
110
|
-
)
|
|
111
|
-
const downloads = chalk.green(
|
|
112
|
-
`↓ ${formatDownloads(pkg.downloadCount ?? pkg.downloads ?? 0)}`.padEnd(
|
|
113
|
-
10,
|
|
114
|
-
),
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
console.log(` ${chalk.cyan(paddedName)} ${version} ${downloads}`)
|
|
118
|
-
|
|
119
|
-
if (pkg.description) {
|
|
120
|
-
const truncatedDesc =
|
|
121
|
-
pkg.description.length > 60
|
|
122
|
-
? `${pkg.description.slice(0, 57)}...`
|
|
123
|
-
: pkg.description
|
|
124
|
-
console.log(` ${chalk.dim(truncatedDesc)}`)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
console.log("")
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (packages.length === limit) {
|
|
131
|
-
console.log(
|
|
132
|
-
chalk.dim(` Showing first ${limit} results. Use --limit to see more.`),
|
|
133
|
-
)
|
|
134
|
-
console.log("")
|
|
135
|
-
}
|
|
136
|
-
} catch (error) {
|
|
137
|
-
spinner.fail(chalk.red("Search failed."))
|
|
138
|
-
console.error(chalk.red(` ${error.message}`))
|
|
139
|
-
process.exit(1)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export default search
|
package/lib/commands/setup.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import * as p from "@clack/prompts";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { getRegistryUrl } from "../config.js";
|
|
6
|
-
import { exchangeOidcInstallToken } from "../oidc.js";
|
|
7
|
-
import { log, printHeader } from "../ui.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Remove existing LPM config lines from .npmrc content.
|
|
11
|
-
* Handles both scoped (@lpm.dev:registry=) and unscoped (registry=...lpm.dev) formats.
|
|
12
|
-
* @param {string} content
|
|
13
|
-
* @returns {string}
|
|
14
|
-
*/
|
|
15
|
-
function removeLpmLines(content) {
|
|
16
|
-
return content
|
|
17
|
-
.split("\n")
|
|
18
|
-
.filter((line) => {
|
|
19
|
-
return (
|
|
20
|
-
!line.includes("@lpm.dev:registry") &&
|
|
21
|
-
!line.match(/^registry=.*lpm\.dev/) &&
|
|
22
|
-
!line.includes("lpm.dev/api/registry/:_authToken") &&
|
|
23
|
-
!line.includes("_authToken=lpm_") &&
|
|
24
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: matching literal npm token placeholder
|
|
25
|
-
!line.includes("_authToken=${LPM_TOKEN}") &&
|
|
26
|
-
!line.includes("# LPM Registry")
|
|
27
|
-
);
|
|
28
|
-
})
|
|
29
|
-
.join("\n")
|
|
30
|
-
.trim();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Check if .npmrc has a custom default registry that is NOT npmjs.org or lpm.dev.
|
|
35
|
-
* @param {string} content - Existing .npmrc content
|
|
36
|
-
* @returns {string|null} The custom registry line, or null if none found
|
|
37
|
-
*/
|
|
38
|
-
function detectCustomRegistry(content) {
|
|
39
|
-
return (
|
|
40
|
-
content
|
|
41
|
-
.split("\n")
|
|
42
|
-
.find(
|
|
43
|
-
(line) =>
|
|
44
|
-
line.match(/^registry=/) &&
|
|
45
|
-
!line.includes("registry.npmjs.org") &&
|
|
46
|
-
!line.includes("lpm.dev"),
|
|
47
|
-
) || null
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Configure CI/CD .npmrc for LPM packages.
|
|
53
|
-
*
|
|
54
|
-
* Default mode: writes ${LPM_TOKEN} placeholder (for CI with env var).
|
|
55
|
-
* --oidc mode: exchanges CI OIDC token for a real short-lived token (no secrets needed).
|
|
56
|
-
*/
|
|
57
|
-
export async function setup(options) {
|
|
58
|
-
printHeader();
|
|
59
|
-
|
|
60
|
-
p.intro(chalk.bgCyan(chalk.black(" lpm setup ci ")));
|
|
61
|
-
|
|
62
|
-
const registryUrl = options?.registry || getRegistryUrl();
|
|
63
|
-
const projectRoot = process.cwd();
|
|
64
|
-
const npmrcPath = path.join(projectRoot, ".npmrc");
|
|
65
|
-
|
|
66
|
-
// Check for package.json
|
|
67
|
-
const pkgJsonPath = path.join(projectRoot, "package.json");
|
|
68
|
-
if (!fs.existsSync(pkgJsonPath)) {
|
|
69
|
-
log.warn("No package.json found. Run this command in your project root.");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Build registry URL
|
|
73
|
-
const fullRegistryUrl = registryUrl.endsWith("/api/registry")
|
|
74
|
-
? registryUrl
|
|
75
|
-
: `${registryUrl}/api/registry`;
|
|
76
|
-
const registryHost = fullRegistryUrl.replace(/^https?:/, "");
|
|
77
|
-
|
|
78
|
-
// Default: scoped (only @lpm.dev packages through lpm.dev).
|
|
79
|
-
// --proxy: route all npm traffic through lpm.dev (Pro/Org feature).
|
|
80
|
-
let useScoped = !options?.proxy;
|
|
81
|
-
|
|
82
|
-
// ── OIDC mode: exchange CI token for a real short-lived install token ──
|
|
83
|
-
if (options?.oidc) {
|
|
84
|
-
let oidcToken;
|
|
85
|
-
try {
|
|
86
|
-
oidcToken = await exchangeOidcInstallToken();
|
|
87
|
-
} catch (err) {
|
|
88
|
-
log.warn(`OIDC exchange failed: ${err.message}`);
|
|
89
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: literal placeholder name in user-facing message
|
|
90
|
-
log.warn(
|
|
91
|
-
"Falling back to ${LPM_TOKEN} environment variable placeholder.",
|
|
92
|
-
);
|
|
93
|
-
// Fall through to default mode
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (oidcToken) {
|
|
97
|
-
let npmrcContent = "";
|
|
98
|
-
if (fs.existsSync(npmrcPath)) {
|
|
99
|
-
const existingContent = fs.readFileSync(npmrcPath, "utf8");
|
|
100
|
-
|
|
101
|
-
// Detect custom default registry
|
|
102
|
-
if (!useScoped) {
|
|
103
|
-
const customRegistry = detectCustomRegistry(existingContent);
|
|
104
|
-
if (customRegistry) {
|
|
105
|
-
log.warn(
|
|
106
|
-
`Found existing default registry: ${customRegistry.trim()}`,
|
|
107
|
-
);
|
|
108
|
-
log.warn("Using --scoped mode to avoid overriding it.");
|
|
109
|
-
useScoped = true;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
npmrcContent = removeLpmLines(existingContent);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const registryLine = useScoped
|
|
117
|
-
? `@lpm.dev:registry=${fullRegistryUrl}`
|
|
118
|
-
: `registry=${fullRegistryUrl}`;
|
|
119
|
-
|
|
120
|
-
const lpmConfig = `# LPM Registry (OIDC — expires in 30 minutes)
|
|
121
|
-
${registryLine}
|
|
122
|
-
${registryHost}/:_authToken=${oidcToken}`;
|
|
123
|
-
|
|
124
|
-
npmrcContent = npmrcContent
|
|
125
|
-
? `${npmrcContent}\n\n${lpmConfig}`
|
|
126
|
-
: lpmConfig;
|
|
127
|
-
fs.writeFileSync(npmrcPath, `${npmrcContent}\n`);
|
|
128
|
-
|
|
129
|
-
p.note(
|
|
130
|
-
`${registryLine}\nToken: OIDC read-only (30 min)`,
|
|
131
|
-
".npmrc configuration",
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
console.log("");
|
|
135
|
-
log.success(".npmrc configured with OIDC read-only token.");
|
|
136
|
-
log.info("Token expires in 30 minutes. No LPM_TOKEN secret needed.");
|
|
137
|
-
if (!useScoped) {
|
|
138
|
-
log.info("All packages (LPM + npm) will route through lpm.dev.");
|
|
139
|
-
}
|
|
140
|
-
console.log("");
|
|
141
|
-
|
|
142
|
-
p.outro("Setup complete!");
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ── Default mode: ${LPM_TOKEN} placeholder ──
|
|
148
|
-
|
|
149
|
-
// Check for existing .npmrc
|
|
150
|
-
let npmrcContent = "";
|
|
151
|
-
if (fs.existsSync(npmrcPath)) {
|
|
152
|
-
npmrcContent = fs.readFileSync(npmrcPath, "utf8");
|
|
153
|
-
|
|
154
|
-
// Detect custom default registry
|
|
155
|
-
if (!useScoped) {
|
|
156
|
-
const customRegistry = detectCustomRegistry(npmrcContent);
|
|
157
|
-
if (customRegistry) {
|
|
158
|
-
log.warn(`Found existing default registry: ${customRegistry.trim()}`);
|
|
159
|
-
log.warn("Using --scoped mode to avoid overriding it.");
|
|
160
|
-
log.warn(
|
|
161
|
-
"To use LPM as your default registry, remove the existing registry= line first.",
|
|
162
|
-
);
|
|
163
|
-
useScoped = true;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Check if already configured (both scoped and unscoped formats)
|
|
168
|
-
if (
|
|
169
|
-
npmrcContent.includes("@lpm.dev:registry") ||
|
|
170
|
-
npmrcContent.match(/^registry=.*lpm\.dev/m)
|
|
171
|
-
) {
|
|
172
|
-
const overwrite = await p.confirm({
|
|
173
|
-
message: ".npmrc already has LPM configuration. Overwrite?",
|
|
174
|
-
initialValue: false,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (p.isCancel(overwrite) || !overwrite) {
|
|
178
|
-
p.outro("Setup cancelled.");
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
npmrcContent = removeLpmLines(npmrcContent);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const registryLine = useScoped
|
|
187
|
-
? `@lpm.dev:registry=${fullRegistryUrl}`
|
|
188
|
-
: `registry=${fullRegistryUrl}`;
|
|
189
|
-
|
|
190
|
-
// Add LPM registry config with placeholder
|
|
191
|
-
const lpmConfig = `
|
|
192
|
-
# LPM Registry
|
|
193
|
-
${registryLine}
|
|
194
|
-
${registryHost}/:_authToken=\${LPM_TOKEN}
|
|
195
|
-
`.trim();
|
|
196
|
-
|
|
197
|
-
npmrcContent = npmrcContent ? `${npmrcContent}\n\n${lpmConfig}` : lpmConfig;
|
|
198
|
-
fs.writeFileSync(npmrcPath, `${npmrcContent}\n`);
|
|
199
|
-
|
|
200
|
-
p.note(
|
|
201
|
-
`${registryLine}\n${registryHost}/:_authToken=\${LPM_TOKEN}`,
|
|
202
|
-
".npmrc configuration",
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
console.log("");
|
|
206
|
-
log.success(".npmrc configured for LPM registry.");
|
|
207
|
-
if (useScoped) {
|
|
208
|
-
log.info("Only @lpm.dev packages will route through lpm.dev.");
|
|
209
|
-
} else {
|
|
210
|
-
log.info("All packages (LPM + npm) will route through lpm.dev.");
|
|
211
|
-
log.info("Note: First install may update resolved URLs in your lockfile.");
|
|
212
|
-
}
|
|
213
|
-
log.info("For local development: Run `lpm setup local` to generate a token.");
|
|
214
|
-
log.info("For CI/CD: Set the LPM_TOKEN environment variable.");
|
|
215
|
-
log.info(
|
|
216
|
-
"For CI/CD with OIDC: Run `lpm setup ci --oidc` (no secrets needed).",
|
|
217
|
-
);
|
|
218
|
-
console.log("");
|
|
219
|
-
|
|
220
|
-
p.outro("Setup complete!");
|
|
221
|
-
}
|