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