@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,25 +0,0 @@
1
- import { request } from "../api.js"
2
- import { setToken } from "../config.js"
3
- import { createSpinner, printHeader } from "../ui.js"
4
-
5
- export async function rotateToken() {
6
- printHeader()
7
- const spinner = createSpinner("Rotating token...").start()
8
- try {
9
- const response = await request("/-/token/rotate", {
10
- method: "POST",
11
- })
12
-
13
- if (!response.ok) {
14
- throw new Error(`Request failed with status ${response.status}`)
15
- }
16
-
17
- const data = await response.json()
18
- const newToken = data.token
19
-
20
- await setToken(newToken)
21
- spinner.succeed("Token rotated successfully! Local config updated.")
22
- } catch (error) {
23
- spinner.fail(`Error: ${error.message}`)
24
- }
25
- }
@@ -1,107 +0,0 @@
1
- import { spawn } from "node:child_process"
2
- import fs from "node:fs"
3
- import path from "node:path"
4
- import { getPackageManager, getRegistryUrl, getToken } from "../config.js"
5
- import { createSpinner, log, printHeader } from "../ui.js"
6
-
7
- /**
8
- * Uninstall LPM packages.
9
- * Creates a temporary .npmrc with auth, runs the package manager's uninstall, and cleans up.
10
- *
11
- * @param {string[]} packages - Package names to uninstall
12
- * @param {object} [options]
13
- * @param {string} [options.pm] - Package manager override (npm, pnpm, yarn, bun)
14
- */
15
- export async function uninstall(packages, options) {
16
- printHeader()
17
-
18
- if (!packages || packages.length === 0) {
19
- log.error("Please specify at least one package to uninstall.")
20
- log.info("Usage: lpm uninstall @lpm.dev/owner.package-name")
21
- process.exit(1)
22
- }
23
-
24
- const token = await getToken()
25
- if (!token) {
26
- log.error(
27
- 'You must be logged in to uninstall packages. Run "lpm login" first.',
28
- )
29
- process.exit(1)
30
- }
31
-
32
- const pm = getPackageManager(options?.pm)
33
- const spinner = createSpinner(
34
- `Preparing to uninstall ${packages.join(", ")}...`,
35
- ).start()
36
-
37
- const baseRegistryUrl = getRegistryUrl()
38
- const registryUrl = baseRegistryUrl.endsWith("/api/registry")
39
- ? baseRegistryUrl
40
- : `${baseRegistryUrl}/api/registry`
41
- const registryHost = registryUrl.replace(/^https?:/, "")
42
-
43
- const npmrcContent = `${registryHost}/:_authToken=${token}
44
- @lpm.dev:registry=${registryUrl}
45
- `
46
-
47
- const projectNpmrcPath = path.resolve(process.cwd(), ".npmrc")
48
- let existingNpmrc = null
49
- let tempNpmrcPath = null
50
-
51
- if (pm === "npm") {
52
- tempNpmrcPath = path.resolve(process.cwd(), `.npmrc.lpm-${Date.now()}`)
53
- fs.writeFileSync(tempNpmrcPath, npmrcContent)
54
- } else {
55
- if (fs.existsSync(projectNpmrcPath)) {
56
- existingNpmrc = fs.readFileSync(projectNpmrcPath, "utf8")
57
- }
58
- fs.writeFileSync(projectNpmrcPath, npmrcContent)
59
- }
60
-
61
- spinner.succeed(`Configuration generated. Running ${pm} uninstall...`)
62
-
63
- const pmArgs =
64
- pm === "npm"
65
- ? ["uninstall", ...packages, "--userconfig", tempNpmrcPath]
66
- : ["uninstall", ...packages]
67
-
68
- const child = spawn(pm, pmArgs, {
69
- stdio: "inherit",
70
- env: { ...process.env, LPM_TOKEN: token },
71
- })
72
-
73
- const cleanup = () => {
74
- if (pm === "npm") {
75
- if (tempNpmrcPath && fs.existsSync(tempNpmrcPath)) {
76
- fs.unlinkSync(tempNpmrcPath)
77
- }
78
- } else {
79
- if (existingNpmrc !== null) {
80
- fs.writeFileSync(projectNpmrcPath, existingNpmrc)
81
- } else if (fs.existsSync(projectNpmrcPath)) {
82
- fs.unlinkSync(projectNpmrcPath)
83
- }
84
- }
85
- }
86
-
87
- child.on("close", code => {
88
- cleanup()
89
- if (code !== 0) {
90
- log.error(`${pm} uninstall failed with code ${code}`)
91
- process.exit(code)
92
- } else {
93
- log.success("Packages uninstalled successfully.")
94
- }
95
- })
96
-
97
- child.on("error", err => {
98
- log.error(`Failed to start ${pm}: ${err.message}`)
99
- cleanup()
100
- process.exit(1)
101
- })
102
-
103
- process.on("SIGINT", () => {
104
- cleanup()
105
- process.exit()
106
- })
107
- }
@@ -1,129 +0,0 @@
1
- import { request } from "../api.js"
2
- import { getRegistryUrl } from "../config.js"
3
- import { log, printHeader } from "../ui.js"
4
-
5
- export async function whoami(options = {}) {
6
- if (!options.json) printHeader()
7
- try {
8
- const response = await request("/-/whoami")
9
-
10
- if (!response.ok) {
11
- throw new Error(`Request failed with status ${response.status}`)
12
- }
13
-
14
- const data = await response.json()
15
-
16
- if (options.json) {
17
- const result = {
18
- username: data.username,
19
- profileUsername: data.profile_username || null,
20
- email: data.email || null,
21
- plan: data.plan_tier || null,
22
- hasPoolAccess: data.has_pool_access || false,
23
- usage: data.usage || null,
24
- limits: data.limits || null,
25
- orgs: (data.organizations || []).map(org => ({
26
- slug: org.slug,
27
- name: org.name,
28
- role: org.role,
29
- })),
30
- }
31
- console.log(JSON.stringify(result, null, 2))
32
- return
33
- }
34
-
35
- log.success(`Logged in as: ${data.username}`)
36
-
37
- if (data.plan_tier) {
38
- console.log("") // Spacer
39
- log.info(`Plan: ${data.plan_tier.toUpperCase()}`)
40
-
41
- // Pool subscription status
42
- if (data.has_pool_access) {
43
- log.success("Pool: Active")
44
- } else {
45
- log.info("Pool: Not subscribed")
46
- }
47
-
48
- if (data.usage) {
49
- const storageMB = (data.usage.storage_bytes / (1024 * 1024)).toFixed(2)
50
- const limits = data.limits || {}
51
-
52
- // Storage Check - backend returns storageBytes (not storage_gb)
53
- if (limits.storageBytes) {
54
- const limitMB = (limits.storageBytes / (1024 * 1024)).toFixed(0)
55
- const storageMsg = `Storage Used: ${storageMB}MB / ${limitMB}MB`
56
- if (data.usage.storage_bytes > limits.storageBytes) {
57
- log.error(`${storageMsg} (OVER LIMIT)`)
58
- } else {
59
- log.info(storageMsg)
60
- }
61
- } else {
62
- log.info(`Storage Used: ${storageMB}MB`)
63
- }
64
-
65
- // Package Count Check - backend returns privatePackages (not private_packages)
66
- if (limits.privatePackages !== undefined) {
67
- if (
68
- limits.privatePackages === Number.POSITIVE_INFINITY ||
69
- limits.privatePackages === null
70
- ) {
71
- log.info(
72
- `Private Packages: ${data.usage.private_packages} (Unlimited)`,
73
- )
74
- } else {
75
- const pkgMsg = `Private Packages: ${data.usage.private_packages} / ${limits.privatePackages}`
76
- if (data.usage.private_packages > limits.privatePackages) {
77
- log.error(`${pkgMsg} (OVER LIMIT)`)
78
- } else {
79
- log.info(pkgMsg)
80
- }
81
- }
82
- } else {
83
- log.info(`Private Packages: ${data.usage.private_packages}`)
84
- }
85
-
86
- // Over limit warning
87
- const overStorage =
88
- limits.storageBytes && data.usage.storage_bytes > limits.storageBytes
89
- const overPackages =
90
- limits.privatePackages &&
91
- limits.privatePackages !== Number.POSITIVE_INFINITY &&
92
- limits.privatePackages !== null &&
93
- data.usage.private_packages > limits.privatePackages
94
-
95
- if (overStorage || overPackages) {
96
- const registryUrl = getRegistryUrl()
97
- console.log("")
98
- log.warn("Your account is over its plan limits.")
99
- log.warn("Write access (publishing, inviting members) is restricted.")
100
- log.warn(
101
- `Upgrade your plan: ${registryUrl}/dashboard/settings/billing`,
102
- )
103
- }
104
- }
105
- }
106
-
107
- // Display available scopes for publishing
108
- const registryUrl = getRegistryUrl()
109
- console.log("")
110
- log.info("Available Scopes:")
111
-
112
- // Personal scope
113
- if (data.profile_username) {
114
- log.info(` Personal: @lpm.dev/${data.profile_username}.*`)
115
- } else {
116
- log.warn(` Personal: Not set (${registryUrl}/dashboard/settings)`)
117
- }
118
-
119
- // Organization scopes
120
- if (data.organizations?.length > 0) {
121
- log.info(" Organizations:")
122
- for (const org of data.organizations) {
123
- log.info(` @lpm.dev/${org.slug}.* (${org.role})`)
124
- }
125
- }
126
- } catch (error) {
127
- log.error(`Error: ${error.message}`)
128
- }
129
- }
package/lib/config.js DELETED
@@ -1,328 +0,0 @@
1
- /**
2
- * CLI Configuration Management
3
- *
4
- * Handles both secure credential storage and general configuration.
5
- * Token storage migrated to secure-store for keychain integration.
6
- *
7
- * @module cli/lib/config
8
- */
9
-
10
- import Conf from "conf"
11
- import {
12
- DEFAULT_REGISTRY_URL,
13
- MAX_RETRIES,
14
- REQUEST_TIMEOUT_MS,
15
- } from "./constants.js"
16
- import {
17
- isUsingKeychain,
18
- clearToken as secureClearToken,
19
- getToken as secureGetToken,
20
- setToken as secureSetToken,
21
- } from "./secure-store.js"
22
-
23
- // ============================================================================
24
- // General Configuration Store (non-sensitive data)
25
- // ============================================================================
26
-
27
- const config = new Conf({
28
- projectName: "lpm-cli",
29
- defaults: {
30
- registryUrl: DEFAULT_REGISTRY_URL,
31
- timeout: REQUEST_TIMEOUT_MS,
32
- retries: MAX_RETRIES,
33
- },
34
- })
35
-
36
- // ============================================================================
37
- // Token Management (Secure Storage)
38
- // ============================================================================
39
-
40
- /**
41
- * Transient token — set for the duration of an OIDC-authenticated publish.
42
- * Takes priority over all other token sources so the short-lived OIDC token
43
- * is used without writing to keychain or mutating process.env.
44
- * @type {string | null}
45
- */
46
- let _transientToken = null
47
-
48
- /**
49
- * Set a transient token that overrides all other token sources.
50
- * Used by OIDC token exchange to inject a short-lived publish token.
51
- * @param {string} token
52
- */
53
- export function setTransientToken(token) {
54
- _transientToken = token
55
- }
56
-
57
- /**
58
- * Clear the transient token after use.
59
- */
60
- export function clearTransientToken() {
61
- _transientToken = null
62
- }
63
-
64
- /**
65
- * Get the stored auth token from secure storage.
66
- * Priority: transient (OIDC) > LPM_TOKEN env var > secure storage > legacy storage
67
- * Tokens are scoped per registry URL so dev and production don't collide.
68
- * @returns {Promise<string | null>}
69
- */
70
- export async function getToken() {
71
- // OIDC-exchanged short-lived token takes highest priority
72
- if (_transientToken) return _transientToken
73
-
74
- // Check environment variable first (useful for CI/CD and testing)
75
- const envToken = process.env.LPM_TOKEN
76
- if (envToken) return envToken
77
-
78
- const registryUrl = getRegistryUrl()
79
-
80
- // Try secure store (registry-scoped)
81
- const secureToken = await secureGetToken(registryUrl)
82
- if (secureToken) return secureToken
83
-
84
- // Migration: check if token exists in old Conf storage
85
- const legacyToken = config.get("token")
86
- if (legacyToken) {
87
- // Migrate to secure storage (scoped to current registry)
88
- await secureSetToken(legacyToken, registryUrl)
89
- config.delete("token")
90
- return legacyToken
91
- }
92
-
93
- return null
94
- }
95
-
96
- /**
97
- * Set the auth token in secure storage (scoped to current registry).
98
- * @param {string | null} token
99
- * @returns {Promise<void>}
100
- */
101
- export async function setToken(token) {
102
- const registryUrl = getRegistryUrl()
103
- if (token === null) {
104
- await secureClearToken(registryUrl)
105
- } else {
106
- await secureSetToken(token, registryUrl)
107
- }
108
- // Ensure legacy token is cleared
109
- config.delete("token")
110
- }
111
-
112
- /**
113
- * Clear the auth token from secure storage (scoped to current registry).
114
- * @returns {Promise<void>}
115
- */
116
- export async function clearToken() {
117
- await secureClearToken(getRegistryUrl())
118
- config.delete("token")
119
- }
120
-
121
- /**
122
- * Store a refresh token in secure storage (Feature 44 Part B).
123
- * Uses the same secure-store as auth tokens with a "refresh:" prefix.
124
- * @param {string} refreshToken
125
- * @returns {Promise<void>}
126
- */
127
- export async function setRefreshToken(refreshToken) {
128
- const registryUrl = getRegistryUrl()
129
- await secureSetToken(refreshToken, `refresh:${registryUrl}`)
130
- }
131
-
132
- /**
133
- * Get the stored refresh token from secure storage.
134
- * @returns {Promise<string|null>}
135
- */
136
- export async function getRefreshToken() {
137
- const registryUrl = getRegistryUrl()
138
- return secureGetToken(`refresh:${registryUrl}`)
139
- }
140
-
141
- // ============================================================================
142
- // Registry URL Configuration
143
- // ============================================================================
144
-
145
- /**
146
- * Get the registry URL.
147
- * Priority: LPM_REGISTRY_URL env var > stored config > default
148
- * @returns {string}
149
- */
150
- export function getRegistryUrl() {
151
- // Check environment variables first (useful for CI/CD and testing)
152
- const envUrl = process.env.LPM_REGISTRY_URL
153
- if (envUrl) return envUrl
154
-
155
- return config.get("registryUrl", DEFAULT_REGISTRY_URL)
156
- }
157
-
158
- /**
159
- * Set the registry URL.
160
- * @param {string} url
161
- */
162
- export function setRegistryUrl(url) {
163
- config.set("registryUrl", url)
164
- }
165
-
166
- // ============================================================================
167
- // Timeout Configuration
168
- // ============================================================================
169
-
170
- /**
171
- * Get the request timeout in milliseconds.
172
- * @returns {number}
173
- */
174
- export function getTimeout() {
175
- return config.get("timeout", REQUEST_TIMEOUT_MS)
176
- }
177
-
178
- /**
179
- * Set the request timeout in milliseconds.
180
- * @param {number} ms
181
- */
182
- export function setTimeout(ms) {
183
- config.set("timeout", ms)
184
- }
185
-
186
- // ============================================================================
187
- // Retry Configuration
188
- // ============================================================================
189
-
190
- /**
191
- * Get the maximum retry count.
192
- * @returns {number}
193
- */
194
- export function getRetries() {
195
- return config.get("retries", MAX_RETRIES)
196
- }
197
-
198
- /**
199
- * Set the maximum retry count.
200
- * @param {number} count
201
- */
202
- export function setRetries(count) {
203
- config.set("retries", count)
204
- }
205
-
206
- // ============================================================================
207
- // Package Manager Configuration
208
- // ============================================================================
209
-
210
- const VALID_PACKAGE_MANAGERS = new Set(["npm", "pnpm", "yarn", "bun"])
211
- const DEFAULT_PACKAGE_MANAGER = "npm"
212
-
213
- /**
214
- * Get the configured package manager for install/uninstall operations.
215
- * Priority: --pm flag > LPM_PACKAGE_MANAGER env > config > "npm"
216
- * @param {string} [override] - CLI flag override (--pm value)
217
- * @returns {string}
218
- */
219
- export function getPackageManager(override) {
220
- const pm =
221
- override ||
222
- process.env.LPM_PACKAGE_MANAGER ||
223
- config.get("packageManager", DEFAULT_PACKAGE_MANAGER)
224
-
225
- if (!VALID_PACKAGE_MANAGERS.has(pm)) {
226
- return DEFAULT_PACKAGE_MANAGER
227
- }
228
-
229
- return pm
230
- }
231
-
232
- // ============================================================================
233
- // General Configuration Access
234
- // ============================================================================
235
-
236
- /**
237
- * Get all configuration values.
238
- * @returns {Promise<Record<string, unknown>>}
239
- */
240
- export async function getAllConfig() {
241
- const usingKeychain = await isUsingKeychain()
242
- const hasToken = !!(await getToken())
243
-
244
- return {
245
- registryUrl: getRegistryUrl(),
246
- packageManager: getPackageManager(),
247
- timeout: getTimeout(),
248
- retries: getRetries(),
249
- secureStorage: usingKeychain ? "keychain" : "encrypted-file",
250
- authenticated: hasToken,
251
- }
252
- }
253
-
254
- /**
255
- * Get a specific configuration value.
256
- * @param {string} key
257
- * @returns {unknown}
258
- */
259
- export function getConfigValue(key) {
260
- const configMap = {
261
- registry: getRegistryUrl,
262
- registryUrl: getRegistryUrl,
263
- packageManager: getPackageManager,
264
- timeout: getTimeout,
265
- retries: getRetries,
266
- }
267
-
268
- const getter = configMap[key]
269
- if (getter) return getter()
270
- return config.get(key)
271
- }
272
-
273
- /**
274
- * Set a specific configuration value.
275
- * @param {string} key
276
- * @param {unknown} value
277
- * @returns {boolean} - True if set successfully
278
- */
279
- export function setConfigValue(key, value) {
280
- const configMap = {
281
- registry: setRegistryUrl,
282
- registryUrl: setRegistryUrl,
283
- packageManager: val => {
284
- if (VALID_PACKAGE_MANAGERS.has(val)) {
285
- config.set("packageManager", val)
286
- } else {
287
- throw new Error(
288
- `Invalid package manager "${val}". Valid options: ${[...VALID_PACKAGE_MANAGERS].join(", ")}`,
289
- )
290
- }
291
- },
292
- timeout: val => setTimeout(Number(val)),
293
- retries: val => setRetries(Number(val)),
294
- }
295
-
296
- const setter = configMap[key]
297
- if (setter) {
298
- setter(value)
299
- return true
300
- }
301
-
302
- // Allow setting arbitrary config values
303
- config.set(key, value)
304
- return true
305
- }
306
-
307
- /**
308
- * Delete a specific configuration value.
309
- * @param {string} key
310
- * @returns {boolean} - True if deleted
311
- */
312
- export function deleteConfigValue(key) {
313
- const protectedKeys = ["registryUrl", "registry", "timeout", "retries"]
314
- if (protectedKeys.includes(key)) {
315
- // Reset to default instead of deleting
316
- const defaults = {
317
- registryUrl: DEFAULT_REGISTRY_URL,
318
- registry: DEFAULT_REGISTRY_URL,
319
- timeout: REQUEST_TIMEOUT_MS,
320
- retries: MAX_RETRIES,
321
- }
322
- config.set(key === "registry" ? "registryUrl" : key, defaults[key])
323
- return true
324
- }
325
-
326
- config.delete(key)
327
- return true
328
- }