@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,547 @@
|
|
|
1
|
+
import http from "node:http"
|
|
2
|
+
import open from "open"
|
|
3
|
+
import { request } from "../api.js"
|
|
4
|
+
import { getRegistryUrl, setToken } from "../config.js"
|
|
5
|
+
import { WARNING_MESSAGES } from "../constants.js"
|
|
6
|
+
import { createSpinner, log, printHeader } from "../ui.js"
|
|
7
|
+
|
|
8
|
+
function getErrorHtml(title, message) {
|
|
9
|
+
return `
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html>
|
|
12
|
+
<head>
|
|
13
|
+
<title>LPM - ${title}</title>
|
|
14
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@400;500;600&display=swap" rel="stylesheet">
|
|
17
|
+
<style>
|
|
18
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
19
|
+
|
|
20
|
+
:root {
|
|
21
|
+
--bg-primary: #0a0a0b;
|
|
22
|
+
--bg-card: #111113;
|
|
23
|
+
--bg-subtle: #18181b;
|
|
24
|
+
--border: #27272a;
|
|
25
|
+
--text-primary: #fafafa;
|
|
26
|
+
--text-secondary: #a1a1aa;
|
|
27
|
+
--text-muted: #71717a;
|
|
28
|
+
--error: #ef4444;
|
|
29
|
+
--error-glow: rgba(239, 68, 68, 0.15);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
body {
|
|
33
|
+
font-family: 'Outfit', system-ui, sans-serif;
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
align-items: center;
|
|
38
|
+
background: var(--bg-primary);
|
|
39
|
+
background-image:
|
|
40
|
+
radial-gradient(ellipse 80% 50% at 50% -20%, var(--error-glow), transparent),
|
|
41
|
+
radial-gradient(circle at 50% 50%, var(--bg-primary), var(--bg-primary));
|
|
42
|
+
padding: 1.5rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.container {
|
|
46
|
+
width: 100%;
|
|
47
|
+
max-width: 420px;
|
|
48
|
+
animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@keyframes fadeInUp {
|
|
52
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
53
|
+
to { opacity: 1; transform: translateY(0); }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.card {
|
|
57
|
+
background: var(--bg-card);
|
|
58
|
+
border: 1px solid var(--border);
|
|
59
|
+
border-radius: 16px;
|
|
60
|
+
padding: 2.5rem 2rem;
|
|
61
|
+
text-align: center;
|
|
62
|
+
position: relative;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.card::before {
|
|
67
|
+
content: '';
|
|
68
|
+
position: absolute;
|
|
69
|
+
top: 0;
|
|
70
|
+
left: 50%;
|
|
71
|
+
transform: translateX(-50%);
|
|
72
|
+
width: 200px;
|
|
73
|
+
height: 1px;
|
|
74
|
+
background: linear-gradient(90deg, transparent, var(--error), transparent);
|
|
75
|
+
opacity: 0.6;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.icon-wrapper {
|
|
79
|
+
width: 80px;
|
|
80
|
+
height: 80px;
|
|
81
|
+
margin: 0 auto 1.5rem;
|
|
82
|
+
position: relative;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.icon-circle {
|
|
89
|
+
width: 64px;
|
|
90
|
+
height: 64px;
|
|
91
|
+
background: var(--error);
|
|
92
|
+
border-radius: 50%;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
box-shadow: 0 0 40px var(--error-glow), 0 0 80px var(--error-glow);
|
|
97
|
+
animation: scaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.1s both;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes scaleIn {
|
|
101
|
+
from { transform: scale(0); }
|
|
102
|
+
to { transform: scale(1); }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.x-icon {
|
|
106
|
+
width: 28px;
|
|
107
|
+
height: 28px;
|
|
108
|
+
stroke: var(--bg-primary);
|
|
109
|
+
stroke-width: 3;
|
|
110
|
+
stroke-linecap: round;
|
|
111
|
+
fill: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.title {
|
|
115
|
+
font-size: 1.5rem;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
color: var(--text-primary);
|
|
118
|
+
margin-bottom: 0.75rem;
|
|
119
|
+
letter-spacing: -0.02em;
|
|
120
|
+
animation: fadeIn 0.5s ease-out 0.3s both;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@keyframes fadeIn {
|
|
124
|
+
from { opacity: 0; }
|
|
125
|
+
to { opacity: 1; }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.message {
|
|
129
|
+
font-size: 0.9375rem;
|
|
130
|
+
color: var(--text-secondary);
|
|
131
|
+
line-height: 1.6;
|
|
132
|
+
margin-bottom: 1.5rem;
|
|
133
|
+
animation: fadeIn 0.5s ease-out 0.4s both;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.terminal-hint {
|
|
137
|
+
padding: 0.75rem 1rem;
|
|
138
|
+
background: var(--bg-subtle);
|
|
139
|
+
border-radius: 8px;
|
|
140
|
+
font-family: 'JetBrains Mono', monospace;
|
|
141
|
+
font-size: 0.75rem;
|
|
142
|
+
color: var(--text-muted);
|
|
143
|
+
animation: fadeIn 0.5s ease-out 0.5s both;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.terminal-hint code {
|
|
147
|
+
color: var(--error);
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
150
|
+
</head>
|
|
151
|
+
<body>
|
|
152
|
+
<div class="container">
|
|
153
|
+
<div class="card">
|
|
154
|
+
<div class="icon-wrapper">
|
|
155
|
+
<div class="icon-circle">
|
|
156
|
+
<svg class="x-icon" viewBox="0 0 24 24">
|
|
157
|
+
<path d="M18 6L6 18M6 6l12 12"/>
|
|
158
|
+
</svg>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<h1 class="title">${title}</h1>
|
|
163
|
+
<p class="message">${message}</p>
|
|
164
|
+
|
|
165
|
+
<div class="terminal-hint">
|
|
166
|
+
Run <code>lpm login</code> in your terminal to try again
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</body>
|
|
171
|
+
</html>`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function login() {
|
|
175
|
+
printHeader()
|
|
176
|
+
const registryUrl = getRegistryUrl()
|
|
177
|
+
const spinner = createSpinner(`Logging in to ${registryUrl}...`).start()
|
|
178
|
+
|
|
179
|
+
function closeAndExit(res) {
|
|
180
|
+
res.on("finish", () => {
|
|
181
|
+
server.close(() => process.exit(0))
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const server = http.createServer(async (req, res) => {
|
|
186
|
+
const url = new URL(req.url, `http://${req.headers.host}`)
|
|
187
|
+
|
|
188
|
+
res.setHeader("Access-Control-Allow-Origin", "*")
|
|
189
|
+
|
|
190
|
+
if (url.pathname === "/callback") {
|
|
191
|
+
const token = url.searchParams.get("token")
|
|
192
|
+
|
|
193
|
+
if (token) {
|
|
194
|
+
// Use async setToken
|
|
195
|
+
await setToken(token)
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const response = await request("/-/whoami")
|
|
199
|
+
if (response.ok) {
|
|
200
|
+
const data = await response.json()
|
|
201
|
+
spinner.succeed(`Successfully logged in as: ${data.username}`)
|
|
202
|
+
|
|
203
|
+
// Show warning if personal username is not set
|
|
204
|
+
if (!data.profile_username) {
|
|
205
|
+
console.log("")
|
|
206
|
+
log.warn(WARNING_MESSAGES.usernameNotSet)
|
|
207
|
+
log.warn(WARNING_MESSAGES.usernameNotSetHint(registryUrl))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const html = `
|
|
211
|
+
<!DOCTYPE html>
|
|
212
|
+
<html>
|
|
213
|
+
<head>
|
|
214
|
+
<title>LPM - Access Granted</title>
|
|
215
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
216
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
217
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@400;500;600&display=swap" rel="stylesheet">
|
|
218
|
+
<style>
|
|
219
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
220
|
+
|
|
221
|
+
:root {
|
|
222
|
+
--bg-primary: #0a0a0b;
|
|
223
|
+
--bg-card: #111113;
|
|
224
|
+
--bg-subtle: #18181b;
|
|
225
|
+
--border: #27272a;
|
|
226
|
+
--text-primary: #fafafa;
|
|
227
|
+
--text-secondary: #a1a1aa;
|
|
228
|
+
--text-muted: #71717a;
|
|
229
|
+
--accent: #22c55e;
|
|
230
|
+
--accent-glow: rgba(34, 197, 94, 0.15);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
body {
|
|
234
|
+
font-family: 'Outfit', system-ui, sans-serif;
|
|
235
|
+
min-height: 100vh;
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
align-items: center;
|
|
239
|
+
background: var(--bg-primary);
|
|
240
|
+
background-image:
|
|
241
|
+
radial-gradient(ellipse 80% 50% at 50% -20%, var(--accent-glow), transparent),
|
|
242
|
+
radial-gradient(circle at 50% 50%, var(--bg-primary), var(--bg-primary));
|
|
243
|
+
padding: 1.5rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.container {
|
|
247
|
+
width: 100%;
|
|
248
|
+
max-width: 420px;
|
|
249
|
+
animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@keyframes fadeInUp {
|
|
253
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
254
|
+
to { opacity: 1; transform: translateY(0); }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.card {
|
|
258
|
+
background: var(--bg-card);
|
|
259
|
+
border: 1px solid var(--border);
|
|
260
|
+
border-radius: 16px;
|
|
261
|
+
padding: 2.5rem 2rem;
|
|
262
|
+
text-align: center;
|
|
263
|
+
position: relative;
|
|
264
|
+
overflow: hidden;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.card::before {
|
|
268
|
+
content: '';
|
|
269
|
+
position: absolute;
|
|
270
|
+
top: 0;
|
|
271
|
+
left: 50%;
|
|
272
|
+
transform: translateX(-50%);
|
|
273
|
+
width: 200px;
|
|
274
|
+
height: 1px;
|
|
275
|
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
|
276
|
+
opacity: 0.6;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.icon-wrapper {
|
|
280
|
+
width: 80px;
|
|
281
|
+
height: 80px;
|
|
282
|
+
margin: 0 auto 1.5rem;
|
|
283
|
+
position: relative;
|
|
284
|
+
display: flex;
|
|
285
|
+
align-items: center;
|
|
286
|
+
justify-content: center;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.icon-ring {
|
|
290
|
+
position: absolute;
|
|
291
|
+
inset: 0;
|
|
292
|
+
border-radius: 50%;
|
|
293
|
+
border: 2px solid var(--accent);
|
|
294
|
+
opacity: 0;
|
|
295
|
+
animation: ringPulse 2s ease-out 0.3s infinite;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.icon-ring:nth-child(2) { animation-delay: 0.6s; }
|
|
299
|
+
|
|
300
|
+
@keyframes ringPulse {
|
|
301
|
+
0% { transform: scale(1); opacity: 0.6; }
|
|
302
|
+
100% { transform: scale(1.8); opacity: 0; }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.icon-circle {
|
|
306
|
+
width: 64px;
|
|
307
|
+
height: 64px;
|
|
308
|
+
background: var(--accent);
|
|
309
|
+
border-radius: 50%;
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
justify-content: center;
|
|
313
|
+
box-shadow: 0 0 40px var(--accent-glow), 0 0 80px var(--accent-glow);
|
|
314
|
+
animation: scaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.1s both;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@keyframes scaleIn {
|
|
318
|
+
from { transform: scale(0); }
|
|
319
|
+
to { transform: scale(1); }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.checkmark {
|
|
323
|
+
width: 32px;
|
|
324
|
+
height: 32px;
|
|
325
|
+
stroke: var(--bg-primary);
|
|
326
|
+
stroke-width: 3;
|
|
327
|
+
stroke-linecap: round;
|
|
328
|
+
stroke-linejoin: round;
|
|
329
|
+
fill: none;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.checkmark-path {
|
|
333
|
+
stroke-dasharray: 50;
|
|
334
|
+
stroke-dashoffset: 50;
|
|
335
|
+
animation: drawCheck 0.4s ease-out 0.5s forwards;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@keyframes drawCheck {
|
|
339
|
+
to { stroke-dashoffset: 0; }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.title {
|
|
343
|
+
font-size: 1.5rem;
|
|
344
|
+
font-weight: 600;
|
|
345
|
+
color: var(--text-primary);
|
|
346
|
+
margin-bottom: 0.5rem;
|
|
347
|
+
letter-spacing: -0.02em;
|
|
348
|
+
animation: fadeIn 0.5s ease-out 0.3s both;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@keyframes fadeIn {
|
|
352
|
+
from { opacity: 0; }
|
|
353
|
+
to { opacity: 1; }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.subtitle {
|
|
357
|
+
font-size: 0.9375rem;
|
|
358
|
+
color: var(--text-secondary);
|
|
359
|
+
margin-bottom: 1.5rem;
|
|
360
|
+
animation: fadeIn 0.5s ease-out 0.4s both;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.user-badge {
|
|
364
|
+
display: inline-flex;
|
|
365
|
+
align-items: center;
|
|
366
|
+
gap: 0.5rem;
|
|
367
|
+
background: var(--bg-subtle);
|
|
368
|
+
border: 1px solid var(--border);
|
|
369
|
+
border-radius: 8px;
|
|
370
|
+
padding: 0.625rem 1rem;
|
|
371
|
+
margin-bottom: 1.5rem;
|
|
372
|
+
animation: fadeIn 0.5s ease-out 0.5s both;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.user-icon {
|
|
376
|
+
width: 18px;
|
|
377
|
+
height: 18px;
|
|
378
|
+
stroke: var(--text-muted);
|
|
379
|
+
stroke-width: 2;
|
|
380
|
+
fill: none;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.username {
|
|
384
|
+
font-family: 'JetBrains Mono', monospace;
|
|
385
|
+
font-size: 0.875rem;
|
|
386
|
+
font-weight: 500;
|
|
387
|
+
color: var(--text-primary);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.divider {
|
|
391
|
+
height: 1px;
|
|
392
|
+
background: var(--border);
|
|
393
|
+
margin: 1.25rem 0;
|
|
394
|
+
animation: fadeIn 0.5s ease-out 0.6s both;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.footer {
|
|
398
|
+
display: flex;
|
|
399
|
+
align-items: center;
|
|
400
|
+
justify-content: center;
|
|
401
|
+
gap: 0.5rem;
|
|
402
|
+
animation: fadeIn 0.5s ease-out 0.7s both;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.footer-text {
|
|
406
|
+
font-size: 0.8125rem;
|
|
407
|
+
color: var(--text-muted);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.countdown {
|
|
411
|
+
font-family: 'JetBrains Mono', monospace;
|
|
412
|
+
font-size: 0.75rem;
|
|
413
|
+
font-weight: 500;
|
|
414
|
+
color: var(--accent);
|
|
415
|
+
background: var(--accent-glow);
|
|
416
|
+
padding: 0.25rem 0.5rem;
|
|
417
|
+
border-radius: 4px;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.terminal-hint {
|
|
421
|
+
margin-top: 1.5rem;
|
|
422
|
+
padding: 0.75rem 1rem;
|
|
423
|
+
background: var(--bg-subtle);
|
|
424
|
+
border-radius: 8px;
|
|
425
|
+
font-family: 'JetBrains Mono', monospace;
|
|
426
|
+
font-size: 0.75rem;
|
|
427
|
+
color: var(--text-muted);
|
|
428
|
+
animation: fadeIn 0.5s ease-out 0.8s both;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.terminal-hint code {
|
|
432
|
+
color: var(--accent);
|
|
433
|
+
}
|
|
434
|
+
</style>
|
|
435
|
+
</head>
|
|
436
|
+
<body>
|
|
437
|
+
<div class="container">
|
|
438
|
+
<div class="card">
|
|
439
|
+
<div class="icon-wrapper">
|
|
440
|
+
<div class="icon-ring"></div>
|
|
441
|
+
<div class="icon-ring"></div>
|
|
442
|
+
<div class="icon-circle">
|
|
443
|
+
<svg class="checkmark" viewBox="0 0 24 24">
|
|
444
|
+
<path class="checkmark-path" d="M5 12l5 5L19 7"/>
|
|
445
|
+
</svg>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
<h1 class="title">Access Granted</h1>
|
|
450
|
+
<p class="subtitle">CLI authentication successful</p>
|
|
451
|
+
|
|
452
|
+
<div class="user-badge">
|
|
453
|
+
<svg class="user-icon" viewBox="0 0 24 24">
|
|
454
|
+
<circle cx="12" cy="8" r="4"/>
|
|
455
|
+
<path d="M4 21v-2a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v2"/>
|
|
456
|
+
</svg>
|
|
457
|
+
<span class="username">${data.username}</span>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<div class="divider"></div>
|
|
461
|
+
|
|
462
|
+
<div class="footer" id="footer">
|
|
463
|
+
<span class="footer-text">Closing in</span>
|
|
464
|
+
<span class="countdown" id="countdown">5s</span>
|
|
465
|
+
</div>
|
|
466
|
+
|
|
467
|
+
<div class="terminal-hint" id="hint">
|
|
468
|
+
Return to your terminal to continue using <code>lpm</code>
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<script>
|
|
474
|
+
let seconds = 5;
|
|
475
|
+
const countdown = document.getElementById('countdown');
|
|
476
|
+
const footer = document.getElementById('footer');
|
|
477
|
+
const hint = document.getElementById('hint');
|
|
478
|
+
|
|
479
|
+
const interval = setInterval(() => {
|
|
480
|
+
seconds--;
|
|
481
|
+
countdown.textContent = seconds + 's';
|
|
482
|
+
if (seconds <= 0) {
|
|
483
|
+
clearInterval(interval);
|
|
484
|
+
window.close();
|
|
485
|
+
// If window didn't close (browser security), show message
|
|
486
|
+
setTimeout(() => {
|
|
487
|
+
footer.innerHTML = '<span class="footer-text" style="color: var(--accent);">You can close this tab now</span>';
|
|
488
|
+
hint.innerHTML = 'Return to your terminal to continue using <code>lpm</code>';
|
|
489
|
+
}, 100);
|
|
490
|
+
}
|
|
491
|
+
}, 1000);
|
|
492
|
+
</script>
|
|
493
|
+
</body>
|
|
494
|
+
</html>`
|
|
495
|
+
res.setHeader("Content-Type", "text/html")
|
|
496
|
+
closeAndExit(res)
|
|
497
|
+
res.end(html)
|
|
498
|
+
} else {
|
|
499
|
+
console.error("\nToken verification failed.")
|
|
500
|
+
spinner.fail("Token verification failed")
|
|
501
|
+
res.setHeader("Content-Type", "text/html")
|
|
502
|
+
closeAndExit(res)
|
|
503
|
+
res.end(
|
|
504
|
+
getErrorHtml(
|
|
505
|
+
"Invalid Token",
|
|
506
|
+
"The authentication token could not be verified. Please try logging in again.",
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
} catch (err) {
|
|
511
|
+
console.error("\nError verifying token:", err.message)
|
|
512
|
+
spinner.fail("Error verifying token")
|
|
513
|
+
res.setHeader("Content-Type", "text/html")
|
|
514
|
+
closeAndExit(res)
|
|
515
|
+
res.end(
|
|
516
|
+
getErrorHtml(
|
|
517
|
+
"Verification Error",
|
|
518
|
+
"An error occurred while verifying your token. Please try again.",
|
|
519
|
+
),
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
spinner.fail("No token received")
|
|
524
|
+
res.setHeader("Content-Type", "text/html")
|
|
525
|
+
closeAndExit(res)
|
|
526
|
+
res.end(
|
|
527
|
+
getErrorHtml(
|
|
528
|
+
"No Token",
|
|
529
|
+
"No authentication token was received. Please try logging in again.",
|
|
530
|
+
),
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
res.end("LPM CLI Login Server")
|
|
535
|
+
}
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
server.listen(0, async () => {
|
|
539
|
+
const port = server.address().port
|
|
540
|
+
// Assuming registryUrl is the base URL of the Next.js app
|
|
541
|
+
const loginUrl = `${registryUrl}/cli/login?port=${port}`
|
|
542
|
+
|
|
543
|
+
console.log(`Opening browser to: ${loginUrl}`)
|
|
544
|
+
await open(loginUrl)
|
|
545
|
+
console.log("Waiting for authentication...")
|
|
546
|
+
})
|
|
547
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout Command
|
|
3
|
+
*
|
|
4
|
+
* Clears stored authentication token and optionally revokes it on the server.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* lpm logout [options]
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* --revoke Also revoke the token on the server
|
|
11
|
+
* --clear-cache Clear local package cache
|
|
12
|
+
*
|
|
13
|
+
* @module cli/lib/commands/logout
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import chalk from "chalk"
|
|
17
|
+
import ora from "ora"
|
|
18
|
+
import { request } from "../api.js"
|
|
19
|
+
import { clearToken, getToken } from "../config.js"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute the logout command.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} options - Command options
|
|
25
|
+
* @param {boolean} [options.revoke] - Whether to revoke the token on the server
|
|
26
|
+
* @param {boolean} [options.clearCache] - Whether to clear the local cache
|
|
27
|
+
*/
|
|
28
|
+
export async function logout(options = {}) {
|
|
29
|
+
const spinner = ora("Logging out...").start()
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const token = await getToken()
|
|
33
|
+
|
|
34
|
+
if (!token) {
|
|
35
|
+
spinner.info("Not currently logged in.")
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Optionally revoke the token on the server
|
|
40
|
+
if (options.revoke) {
|
|
41
|
+
spinner.text = "Revoking token on server..."
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await request("/tokens/revoke", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({ token }),
|
|
50
|
+
skipRetry: true,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
spinner.text = "Token revoked on server."
|
|
55
|
+
} else {
|
|
56
|
+
// Don't fail logout if revoke fails - just warn
|
|
57
|
+
spinner.text =
|
|
58
|
+
"Could not revoke token on server (continuing with local logout)."
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Don't fail logout if revoke fails
|
|
62
|
+
spinner.text =
|
|
63
|
+
"Could not reach server to revoke token (continuing with local logout)."
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Clear local token
|
|
68
|
+
await clearToken()
|
|
69
|
+
|
|
70
|
+
// Optionally clear cache
|
|
71
|
+
if (options.clearCache) {
|
|
72
|
+
spinner.text = "Clearing local cache..."
|
|
73
|
+
// Import dynamically to avoid circular dependency
|
|
74
|
+
const { clearCache } = await import("./cache.js")
|
|
75
|
+
await clearCache()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
spinner.succeed(chalk.green("Successfully logged out."))
|
|
79
|
+
|
|
80
|
+
if (options.revoke) {
|
|
81
|
+
console.log(chalk.dim(" Token has been revoked on the server."))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (options.clearCache) {
|
|
85
|
+
console.log(chalk.dim(" Local cache has been cleared."))
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
spinner.fail(chalk.red("Logout failed."))
|
|
89
|
+
console.error(chalk.red(` ${error.message}`))
|
|
90
|
+
process.exit(1)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default logout
|